From c027045f940b6df653fcf3960e6c70f7f8946193 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 11:50:30 +0200 Subject: [PATCH 01/15] refactor(tests): migrate test suite and mock-builders to TypeScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates 44 mock-builders, 36 test files, 2 offline-support helpers, 1 test utility (BetterSqlite), and jest-setup from JS to TS. Adds tsconfig.test.json so tests and mock-builders are type-checked (base tsconfig still excludes them from the published build) and a test:typecheck script. Dependency changes: - @types/jest: ^29.5.14 -> ^30.0.0 - jest: ^30.0.0 -> ^30.3.0 - @total-typescript/shoehorn: new devDependency (fromPartial() for type-safe partial mocks, replacing `as any` / `Record`) Caveats: - tsconfig.test.json keeps noImplicitAny: false as a starting point; tightening is follow-up work (Phase 5 of the migration plan). - yarn test:typecheck exists but is not wired into CI yet. Verification: - yarn build passes - yarn lint passes - yarn test:unit shows only the pre-existing SQLite-isolation flake in offline-support/index.test.ts (baseline had 5 failures; now 2 — no regressions). --- package/{jest-setup.js => jest-setup.tsx} | 13 +- package/jest.config.js | 2 +- package/package.json | 6 +- ...offline-feature.js => offline-feature.tsx} | 0 ...mistic-update.js => optimistic-update.tsx} | 0 ...Attachment.test.js => Attachment.test.tsx} | 0 .../{Gallery.test.js => Gallery.test.tsx} | 0 .../{Giphy.test.js => Giphy.test.tsx} | 0 ...ldGallery.test.js => buildGallery.test.ts} | 0 ...put.test.js => AutoCompleteInput.test.tsx} | 0 .../{Channel.test.js => Channel.test.tsx} | 0 ...t.js => isAttachmentEqualHandler.test.tsx} | 0 ...ities.test.js => ownCapabilities.test.tsx} | 0 ...t.js => useMessageListPagination.test.tsx} | 0 ...annelList.test.js => ChannelList.test.tsx} | 0 ...tView.test.js => ChannelListView.test.tsx} | 0 ...ew.test.js => ChannelPreviewView.test.tsx} | 0 .../__tests__/{Chat.test.js => Chat.test.tsx} | 0 .../{Message.test.js => Message.test.tsx} | 0 ...eAuthor.test.js => MessageAuthor.test.tsx} | 0 ...ontent.test.js => MessageContent.test.tsx} | 0 ...mView.test.js => MessageItemView.test.tsx} | 0 ...r.test.js => MessagePinnedHeader.test.tsx} | 0 ...eplies.test.js => MessageReplies.test.tsx} | 0 ...eStatus.test.js => MessageStatus.test.tsx} | 0 ...om.test.js => ReactionListBottom.test.tsx} | 0 ...stTop.test.js => ReactionListTop.test.tsx} | 0 ...st.js.snap => MessageAuthor.test.tsx.snap} | 0 ...snap => MessagePinnedHeader.test.tsx.snap} | 0 ...chButton.test.js => AttachButton.test.tsx} | 0 ...s => AttachmentUploadPreviewList.test.tsx} | 0 ... => AudioAttachmentUploadPreview.test.tsx} | 0 ...tButtons.test.js => InputButtons.test.tsx} | 0 ...poser.test.js => MessageComposer.test.tsx} | 0 ...SendButton.test.js => SendButton.test.tsx} | 0 ...> SendMessageDisallowedIndicator.test.tsx} | 0 ...est.js.snap => AttachButton.test.tsx.snap} | 0 ....test.js.snap => SendButton.test.tsx.snap} | 0 ...ssageList.test.js => MessageList.test.tsx} | 0 ...eSystem.test.js => MessageSystem.test.tsx} | 0 ....test.js => ScrollToBottomButton.test.tsx} | 0 ...cator.test.js => TypingIndicator.test.tsx} | 0 ...st.js.snap => MessageSystem.test.tsx.snap} | 0 ...nap => ScrollToBottomButton.test.tsx.snap} | 0 ....js.snap => TypingIndicator.test.tsx.snap} | 0 .../{Thread.test.js => Thread.test.tsx} | 0 ...read.test.js.snap => Thread.test.tsx.snap} | 0 .../src/mock-builders/api/deleteMessage.js | 18 - .../src/mock-builders/api/deleteMessage.ts | 21 + .../src/mock-builders/api/deleteReaction.js | 19 - .../src/mock-builders/api/deleteReaction.ts | 23 + .../mock-builders/api/{error.js => error.ts} | 18 +- .../mock-builders/api/getOrCreateChannel.ts | 27 +- ...nnels.js => initiateClientWithChannels.ts} | 22 +- .../{queryChannels.js => queryChannels.ts} | 6 +- .../api/{queryMembers.js => queryMembers.ts} | 56 +- .../api/{sendMessage.js => sendMessage.ts} | 9 +- package/src/mock-builders/api/sendReaction.ts | 14 +- .../src/mock-builders/api/threadReplies.js | 16 - .../src/mock-builders/api/threadReplies.ts | 16 + .../{useMockedApis.js => useMockedApis.ts} | 9 +- package/src/mock-builders/api/utils.js | 7 - package/src/mock-builders/api/utils.ts | 16 + .../{attachments.js => attachments.ts} | 32 +- .../src/mock-builders/event/channelDeleted.js | 7 - .../src/mock-builders/event/channelDeleted.ts | 12 + .../src/mock-builders/event/channelHidden.js | 7 - .../src/mock-builders/event/channelHidden.ts | 12 + .../mock-builders/event/channelTruncated.js | 7 - .../mock-builders/event/channelTruncated.ts | 12 + .../src/mock-builders/event/channelUpdated.js | 7 - .../src/mock-builders/event/channelUpdated.ts | 12 + .../src/mock-builders/event/channelVisible.js | 7 - .../src/mock-builders/event/channelVisible.ts | 12 + .../mock-builders/event/connectionChanged.js | 6 - .../mock-builders/event/connectionChanged.ts | 11 + .../event/connectionRecovered.js | 5 - .../event/connectionRecovered.ts | 10 + .../src/mock-builders/event/memberAdded.js | 10 - .../src/mock-builders/event/memberAdded.ts | 19 + .../src/mock-builders/event/memberRemoved.js | 9 - .../src/mock-builders/event/memberRemoved.ts | 18 + .../src/mock-builders/event/memberUpdated.js | 9 - .../src/mock-builders/event/memberUpdated.ts | 18 + .../src/mock-builders/event/messageDeleted.js | 8 - .../src/mock-builders/event/messageDeleted.ts | 17 + package/src/mock-builders/event/messageNew.js | 11 - package/src/mock-builders/event/messageNew.ts | 20 + .../src/mock-builders/event/messageRead.js | 15 - .../src/mock-builders/event/messageRead.ts | 23 + .../src/mock-builders/event/messageUpdated.js | 8 - .../src/mock-builders/event/messageUpdated.ts | 17 + .../event/notificationAddedToChannel.js | 7 - .../event/notificationAddedToChannel.ts | 12 + .../event/notificationChannelMutesUpdated.js | 7 - .../event/notificationChannelMutesUpdated.ts | 12 + .../event/notificationMarkRead.js | 7 - .../event/notificationMarkRead.ts | 12 + .../event/notificationMarkUnread.js | 12 - .../event/notificationMarkUnread.ts | 22 + .../event/notificationMessageNew.js | 7 - .../event/notificationMessageNew.ts | 12 + .../event/notificationMutesUpdated.js | 11 - .../event/notificationMutesUpdated.ts | 16 + .../event/notificationRemovedFromChannel.js | 7 - .../event/notificationRemovedFromChannel.ts | 12 + .../mock-builders/event/reactionDeleted.js | 9 - .../mock-builders/event/reactionDeleted.ts | 25 + .../src/mock-builders/event/reactionNew.js | 9 - .../src/mock-builders/event/reactionNew.ts | 25 + .../mock-builders/event/reactionUpdated.js | 9 - .../mock-builders/event/reactionUpdated.ts | 25 + package/src/mock-builders/event/typing.js | 9 - package/src/mock-builders/event/typing.ts | 18 + .../src/mock-builders/event/userPresence.js | 8 - .../src/mock-builders/event/userPresence.ts | 13 + .../src/mock-builders/event/userUpdated.js | 8 - .../src/mock-builders/event/userUpdated.ts | 13 + .../{attachment.js => attachment.ts} | 27 +- .../src/mock-builders/generator/channel.ts | 68 +- package/src/mock-builders/generator/member.js | 13 - package/src/mock-builders/generator/member.ts | 18 + .../generator/{message.js => message.ts} | 16 +- .../src/mock-builders/generator/reaction.js | 12 - .../src/mock-builders/generator/reaction.ts | 15 + .../generator/{user.js => user.ts} | 36 +- package/src/mock-builders/mock.js | 57 -- package/src/mock-builders/mock.ts | 79 ++ package/src/test-utils/BetterSqlite.js | 36 - package/src/test-utils/BetterSqlite.ts | 38 + ...{Streami18n.test.js => Streami18n.test.ts} | 0 .../{utils.test.js => utils.test.ts} | 0 package/tsconfig.test.json | 15 + package/yarn.lock | 846 ++++++++++-------- 134 files changed, 1401 insertions(+), 881 deletions(-) rename package/{jest-setup.js => jest-setup.tsx} (90%) rename package/src/__tests__/offline-support/{offline-feature.js => offline-feature.tsx} (100%) rename package/src/__tests__/offline-support/{optimistic-update.js => optimistic-update.tsx} (100%) rename package/src/components/Attachment/__tests__/{Attachment.test.js => Attachment.test.tsx} (100%) rename package/src/components/Attachment/__tests__/{Gallery.test.js => Gallery.test.tsx} (100%) rename package/src/components/Attachment/__tests__/{Giphy.test.js => Giphy.test.tsx} (100%) rename package/src/components/Attachment/__tests__/{buildGallery.test.js => buildGallery.test.ts} (100%) rename package/src/components/AutoCompleteInput/__tests__/{AutoCompleteInput.test.js => AutoCompleteInput.test.tsx} (100%) rename package/src/components/Channel/__tests__/{Channel.test.js => Channel.test.tsx} (100%) rename package/src/components/Channel/__tests__/{isAttachmentEqualHandler.test.js => isAttachmentEqualHandler.test.tsx} (100%) rename package/src/components/Channel/__tests__/{ownCapabilities.test.js => ownCapabilities.test.tsx} (100%) rename package/src/components/Channel/__tests__/{useMessageListPagination.test.js => useMessageListPagination.test.tsx} (100%) rename package/src/components/ChannelList/__tests__/{ChannelList.test.js => ChannelList.test.tsx} (100%) rename package/src/components/ChannelList/__tests__/{ChannelListView.test.js => ChannelListView.test.tsx} (100%) rename package/src/components/ChannelPreview/__tests__/{ChannelPreviewView.test.js => ChannelPreviewView.test.tsx} (100%) rename package/src/components/Chat/__tests__/{Chat.test.js => Chat.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{Message.test.js => Message.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{MessageAuthor.test.js => MessageAuthor.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{MessageContent.test.js => MessageContent.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{MessageItemView.test.js => MessageItemView.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{MessagePinnedHeader.test.js => MessagePinnedHeader.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{MessageReplies.test.js => MessageReplies.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{MessageStatus.test.js => MessageStatus.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{ReactionListBottom.test.js => ReactionListBottom.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/{ReactionListTop.test.js => ReactionListTop.test.tsx} (100%) rename package/src/components/Message/MessageItemView/__tests__/__snapshots__/{MessageAuthor.test.js.snap => MessageAuthor.test.tsx.snap} (100%) rename package/src/components/Message/MessageItemView/__tests__/__snapshots__/{MessagePinnedHeader.test.js.snap => MessagePinnedHeader.test.tsx.snap} (100%) rename package/src/components/MessageInput/__tests__/{AttachButton.test.js => AttachButton.test.tsx} (100%) rename package/src/components/MessageInput/__tests__/{AttachmentUploadPreviewList.test.js => AttachmentUploadPreviewList.test.tsx} (100%) rename package/src/components/MessageInput/__tests__/{AudioAttachmentUploadPreview.test.js => AudioAttachmentUploadPreview.test.tsx} (100%) rename package/src/components/MessageInput/__tests__/{InputButtons.test.js => InputButtons.test.tsx} (100%) rename package/src/components/MessageInput/__tests__/{MessageComposer.test.js => MessageComposer.test.tsx} (100%) rename package/src/components/MessageInput/__tests__/{SendButton.test.js => SendButton.test.tsx} (100%) rename package/src/components/MessageInput/__tests__/{SendMessageDisallowedIndicator.test.js => SendMessageDisallowedIndicator.test.tsx} (100%) rename package/src/components/MessageInput/__tests__/__snapshots__/{AttachButton.test.js.snap => AttachButton.test.tsx.snap} (100%) rename package/src/components/MessageInput/__tests__/__snapshots__/{SendButton.test.js.snap => SendButton.test.tsx.snap} (100%) rename package/src/components/MessageList/__tests__/{MessageList.test.js => MessageList.test.tsx} (100%) rename package/src/components/MessageList/__tests__/{MessageSystem.test.js => MessageSystem.test.tsx} (100%) rename package/src/components/MessageList/__tests__/{ScrollToBottomButton.test.js => ScrollToBottomButton.test.tsx} (100%) rename package/src/components/MessageList/__tests__/{TypingIndicator.test.js => TypingIndicator.test.tsx} (100%) rename package/src/components/MessageList/__tests__/__snapshots__/{MessageSystem.test.js.snap => MessageSystem.test.tsx.snap} (100%) rename package/src/components/MessageList/__tests__/__snapshots__/{ScrollToBottomButton.test.js.snap => ScrollToBottomButton.test.tsx.snap} (100%) rename package/src/components/MessageList/__tests__/__snapshots__/{TypingIndicator.test.js.snap => TypingIndicator.test.tsx.snap} (100%) rename package/src/components/Thread/__tests__/{Thread.test.js => Thread.test.tsx} (100%) rename package/src/components/Thread/__tests__/__snapshots__/{Thread.test.js.snap => Thread.test.tsx.snap} (100%) delete mode 100644 package/src/mock-builders/api/deleteMessage.js create mode 100644 package/src/mock-builders/api/deleteMessage.ts delete mode 100644 package/src/mock-builders/api/deleteReaction.js create mode 100644 package/src/mock-builders/api/deleteReaction.ts rename package/src/mock-builders/api/{error.js => error.ts} (51%) rename package/src/mock-builders/api/{initiateClientWithChannels.js => initiateClientWithChannels.ts} (70%) rename package/src/mock-builders/api/{queryChannels.js => queryChannels.ts} (55%) rename package/src/mock-builders/api/{queryMembers.js => queryMembers.ts} (65%) rename package/src/mock-builders/api/{sendMessage.js => sendMessage.ts} (54%) delete mode 100644 package/src/mock-builders/api/threadReplies.js create mode 100644 package/src/mock-builders/api/threadReplies.ts rename package/src/mock-builders/api/{useMockedApis.js => useMockedApis.ts} (68%) delete mode 100644 package/src/mock-builders/api/utils.js create mode 100644 package/src/mock-builders/api/utils.ts rename package/src/mock-builders/{attachments.js => attachments.ts} (51%) delete mode 100644 package/src/mock-builders/event/channelDeleted.js create mode 100644 package/src/mock-builders/event/channelDeleted.ts delete mode 100644 package/src/mock-builders/event/channelHidden.js create mode 100644 package/src/mock-builders/event/channelHidden.ts delete mode 100644 package/src/mock-builders/event/channelTruncated.js create mode 100644 package/src/mock-builders/event/channelTruncated.ts delete mode 100644 package/src/mock-builders/event/channelUpdated.js create mode 100644 package/src/mock-builders/event/channelUpdated.ts delete mode 100644 package/src/mock-builders/event/channelVisible.js create mode 100644 package/src/mock-builders/event/channelVisible.ts delete mode 100644 package/src/mock-builders/event/connectionChanged.js create mode 100644 package/src/mock-builders/event/connectionChanged.ts delete mode 100644 package/src/mock-builders/event/connectionRecovered.js create mode 100644 package/src/mock-builders/event/connectionRecovered.ts delete mode 100644 package/src/mock-builders/event/memberAdded.js create mode 100644 package/src/mock-builders/event/memberAdded.ts delete mode 100644 package/src/mock-builders/event/memberRemoved.js create mode 100644 package/src/mock-builders/event/memberRemoved.ts delete mode 100644 package/src/mock-builders/event/memberUpdated.js create mode 100644 package/src/mock-builders/event/memberUpdated.ts delete mode 100644 package/src/mock-builders/event/messageDeleted.js create mode 100644 package/src/mock-builders/event/messageDeleted.ts delete mode 100644 package/src/mock-builders/event/messageNew.js create mode 100644 package/src/mock-builders/event/messageNew.ts delete mode 100644 package/src/mock-builders/event/messageRead.js create mode 100644 package/src/mock-builders/event/messageRead.ts delete mode 100644 package/src/mock-builders/event/messageUpdated.js create mode 100644 package/src/mock-builders/event/messageUpdated.ts delete mode 100644 package/src/mock-builders/event/notificationAddedToChannel.js create mode 100644 package/src/mock-builders/event/notificationAddedToChannel.ts delete mode 100644 package/src/mock-builders/event/notificationChannelMutesUpdated.js create mode 100644 package/src/mock-builders/event/notificationChannelMutesUpdated.ts delete mode 100644 package/src/mock-builders/event/notificationMarkRead.js create mode 100644 package/src/mock-builders/event/notificationMarkRead.ts delete mode 100644 package/src/mock-builders/event/notificationMarkUnread.js create mode 100644 package/src/mock-builders/event/notificationMarkUnread.ts delete mode 100644 package/src/mock-builders/event/notificationMessageNew.js create mode 100644 package/src/mock-builders/event/notificationMessageNew.ts delete mode 100644 package/src/mock-builders/event/notificationMutesUpdated.js create mode 100644 package/src/mock-builders/event/notificationMutesUpdated.ts delete mode 100644 package/src/mock-builders/event/notificationRemovedFromChannel.js create mode 100644 package/src/mock-builders/event/notificationRemovedFromChannel.ts delete mode 100644 package/src/mock-builders/event/reactionDeleted.js create mode 100644 package/src/mock-builders/event/reactionDeleted.ts delete mode 100644 package/src/mock-builders/event/reactionNew.js create mode 100644 package/src/mock-builders/event/reactionNew.ts delete mode 100644 package/src/mock-builders/event/reactionUpdated.js create mode 100644 package/src/mock-builders/event/reactionUpdated.ts delete mode 100644 package/src/mock-builders/event/typing.js create mode 100644 package/src/mock-builders/event/typing.ts delete mode 100644 package/src/mock-builders/event/userPresence.js create mode 100644 package/src/mock-builders/event/userPresence.ts delete mode 100644 package/src/mock-builders/event/userUpdated.js create mode 100644 package/src/mock-builders/event/userUpdated.ts rename package/src/mock-builders/generator/{attachment.js => attachment.ts} (54%) delete mode 100644 package/src/mock-builders/generator/member.js create mode 100644 package/src/mock-builders/generator/member.ts rename package/src/mock-builders/generator/{message.js => message.ts} (64%) delete mode 100644 package/src/mock-builders/generator/reaction.js create mode 100644 package/src/mock-builders/generator/reaction.ts rename package/src/mock-builders/generator/{user.js => user.ts} (51%) delete mode 100644 package/src/mock-builders/mock.js create mode 100644 package/src/mock-builders/mock.ts delete mode 100644 package/src/test-utils/BetterSqlite.js create mode 100644 package/src/test-utils/BetterSqlite.ts rename package/src/utils/__tests__/{Streami18n.test.js => Streami18n.test.ts} (100%) rename package/src/utils/__tests__/{utils.test.js => utils.test.ts} (100%) create mode 100644 package/tsconfig.test.json diff --git a/package/jest-setup.js b/package/jest-setup.tsx similarity index 90% rename from package/jest-setup.js rename to package/jest-setup.tsx index d4f50afd40..69d8be21ff 100644 --- a/package/jest-setup.js +++ b/package/jest-setup.tsx @@ -1,5 +1,6 @@ /* global require */ -import rn, { FlatList, View } from 'react-native'; +import type { ReactNode } from 'react'; +import { FlatList, View } from 'react-native'; import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock.js'; import mockSafeAreaContext from 'react-native-safe-area-context/jest/mock'; @@ -36,12 +37,18 @@ registerNativeHandlers({ jest.mock('react-native-reanimated', () => { const RNReanimatedmock = require('react-native-reanimated/mock'); - return { ...RNReanimatedmock, runOnUI: (fn) => fn }; + return { ...RNReanimatedmock, runOnUI: (fn: () => unknown) => fn }; }); jest.mock('@react-native-community/netinfo', () => mockRNCNetInfo); -const BottomSheetMock = ({ handleComponent, children }) => ( +const BottomSheetMock = ({ + handleComponent, + children, +}: { + handleComponent: () => ReactNode; + children: ReactNode; +}) => ( {handleComponent()} {children} diff --git a/package/jest.config.js b/package/jest.config.js index 6ebeae4825..76e9b58549 100644 --- a/package/jest.config.js +++ b/package/jest.config.js @@ -9,7 +9,7 @@ module.exports = { setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'], setupFilesAfterEnv: [ '@testing-library/jest-native/extend-expect', - require.resolve('./jest-setup.js'), + require.resolve('./jest-setup.tsx'), ], testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/', '/examples/', '__snapshots__', '/lib/'], diff --git a/package/package.json b/package/package.json index 3cf1d43ef8..426a9a8583 100644 --- a/package/package.json +++ b/package/package.json @@ -37,6 +37,7 @@ "prettier": "prettier --list-different '**/*.{js,ts,tsx,md,json}' eslint.config.mjs ../.prettierrc babel.config.js", "prettier-fix": "prettier --write '**/*.{js,ts,tsx,md,json}' eslint.config.mjs ../.prettierrc babel.config.js", "test:coverage": "yarn test:unit --coverage", + "test:typecheck": "tsc --noEmit -p tsconfig.test.json", "test:unit": "TZ=UTC jest", "validate-translations": "node bin/validate-translations.js", "get-version": "echo $npm_package_version", @@ -127,9 +128,10 @@ "@shopify/flash-list": "^2.1.0", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "13.2.0", + "@total-typescript/shoehorn": "^0.1.2", "@types/better-sqlite3": "^7.6.13", "@types/eslint": "9.6.1", - "@types/jest": "^29.5.14", + "@types/jest": "^30.0.0", "@types/linkify-it": "5.0.0", "@types/lodash": "4.17.16", "@types/mime-types": "2.1.4", @@ -154,7 +156,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-native": "^5.0.0", "i18next-cli": "^1.31.0", - "jest": "^30.0.0", + "jest": "^30.3.0", "moment-timezone": "^0.6.0", "prettier": "^3.5.3", "react": "19.1.0", diff --git a/package/src/__tests__/offline-support/offline-feature.js b/package/src/__tests__/offline-support/offline-feature.tsx similarity index 100% rename from package/src/__tests__/offline-support/offline-feature.js rename to package/src/__tests__/offline-support/offline-feature.tsx diff --git a/package/src/__tests__/offline-support/optimistic-update.js b/package/src/__tests__/offline-support/optimistic-update.tsx similarity index 100% rename from package/src/__tests__/offline-support/optimistic-update.js rename to package/src/__tests__/offline-support/optimistic-update.tsx diff --git a/package/src/components/Attachment/__tests__/Attachment.test.js b/package/src/components/Attachment/__tests__/Attachment.test.tsx similarity index 100% rename from package/src/components/Attachment/__tests__/Attachment.test.js rename to package/src/components/Attachment/__tests__/Attachment.test.tsx diff --git a/package/src/components/Attachment/__tests__/Gallery.test.js b/package/src/components/Attachment/__tests__/Gallery.test.tsx similarity index 100% rename from package/src/components/Attachment/__tests__/Gallery.test.js rename to package/src/components/Attachment/__tests__/Gallery.test.tsx diff --git a/package/src/components/Attachment/__tests__/Giphy.test.js b/package/src/components/Attachment/__tests__/Giphy.test.tsx similarity index 100% rename from package/src/components/Attachment/__tests__/Giphy.test.js rename to package/src/components/Attachment/__tests__/Giphy.test.tsx diff --git a/package/src/components/Attachment/__tests__/buildGallery.test.js b/package/src/components/Attachment/__tests__/buildGallery.test.ts similarity index 100% rename from package/src/components/Attachment/__tests__/buildGallery.test.js rename to package/src/components/Attachment/__tests__/buildGallery.test.ts diff --git a/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.js b/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx similarity index 100% rename from package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.js rename to package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx diff --git a/package/src/components/Channel/__tests__/Channel.test.js b/package/src/components/Channel/__tests__/Channel.test.tsx similarity index 100% rename from package/src/components/Channel/__tests__/Channel.test.js rename to package/src/components/Channel/__tests__/Channel.test.tsx diff --git a/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.js b/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx similarity index 100% rename from package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.js rename to package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx diff --git a/package/src/components/Channel/__tests__/ownCapabilities.test.js b/package/src/components/Channel/__tests__/ownCapabilities.test.tsx similarity index 100% rename from package/src/components/Channel/__tests__/ownCapabilities.test.js rename to package/src/components/Channel/__tests__/ownCapabilities.test.tsx diff --git a/package/src/components/Channel/__tests__/useMessageListPagination.test.js b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx similarity index 100% rename from package/src/components/Channel/__tests__/useMessageListPagination.test.js rename to package/src/components/Channel/__tests__/useMessageListPagination.test.tsx diff --git a/package/src/components/ChannelList/__tests__/ChannelList.test.js b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx similarity index 100% rename from package/src/components/ChannelList/__tests__/ChannelList.test.js rename to package/src/components/ChannelList/__tests__/ChannelList.test.tsx diff --git a/package/src/components/ChannelList/__tests__/ChannelListView.test.js b/package/src/components/ChannelList/__tests__/ChannelListView.test.tsx similarity index 100% rename from package/src/components/ChannelList/__tests__/ChannelListView.test.js rename to package/src/components/ChannelList/__tests__/ChannelListView.test.tsx diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.js b/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx similarity index 100% rename from package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.js rename to package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx diff --git a/package/src/components/Chat/__tests__/Chat.test.js b/package/src/components/Chat/__tests__/Chat.test.tsx similarity index 100% rename from package/src/components/Chat/__tests__/Chat.test.js rename to package/src/components/Chat/__tests__/Chat.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/Message.test.js b/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/Message.test.js rename to package/src/components/Message/MessageItemView/__tests__/Message.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.js b/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.js rename to package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.js b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/MessageContent.test.js rename to package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.js b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.js rename to package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.js b/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.js rename to package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.js b/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.js rename to package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.js b/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.js rename to package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.js b/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.js rename to package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.js b/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.js rename to package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx diff --git a/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageAuthor.test.js.snap b/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageAuthor.test.tsx.snap similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageAuthor.test.js.snap rename to package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageAuthor.test.tsx.snap diff --git a/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessagePinnedHeader.test.js.snap b/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessagePinnedHeader.test.tsx.snap similarity index 100% rename from package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessagePinnedHeader.test.js.snap rename to package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessagePinnedHeader.test.tsx.snap diff --git a/package/src/components/MessageInput/__tests__/AttachButton.test.js b/package/src/components/MessageInput/__tests__/AttachButton.test.tsx similarity index 100% rename from package/src/components/MessageInput/__tests__/AttachButton.test.js rename to package/src/components/MessageInput/__tests__/AttachButton.test.tsx diff --git a/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.js b/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx similarity index 100% rename from package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.js rename to package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx similarity index 100% rename from package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js rename to package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx diff --git a/package/src/components/MessageInput/__tests__/InputButtons.test.js b/package/src/components/MessageInput/__tests__/InputButtons.test.tsx similarity index 100% rename from package/src/components/MessageInput/__tests__/InputButtons.test.js rename to package/src/components/MessageInput/__tests__/InputButtons.test.tsx diff --git a/package/src/components/MessageInput/__tests__/MessageComposer.test.js b/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx similarity index 100% rename from package/src/components/MessageInput/__tests__/MessageComposer.test.js rename to package/src/components/MessageInput/__tests__/MessageComposer.test.tsx diff --git a/package/src/components/MessageInput/__tests__/SendButton.test.js b/package/src/components/MessageInput/__tests__/SendButton.test.tsx similarity index 100% rename from package/src/components/MessageInput/__tests__/SendButton.test.js rename to package/src/components/MessageInput/__tests__/SendButton.test.tsx diff --git a/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.js b/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx similarity index 100% rename from package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.js rename to package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx diff --git a/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap b/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.tsx.snap similarity index 100% rename from package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap rename to package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.tsx.snap diff --git a/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap b/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.tsx.snap similarity index 100% rename from package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap rename to package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.tsx.snap diff --git a/package/src/components/MessageList/__tests__/MessageList.test.js b/package/src/components/MessageList/__tests__/MessageList.test.tsx similarity index 100% rename from package/src/components/MessageList/__tests__/MessageList.test.js rename to package/src/components/MessageList/__tests__/MessageList.test.tsx diff --git a/package/src/components/MessageList/__tests__/MessageSystem.test.js b/package/src/components/MessageList/__tests__/MessageSystem.test.tsx similarity index 100% rename from package/src/components/MessageList/__tests__/MessageSystem.test.js rename to package/src/components/MessageList/__tests__/MessageSystem.test.tsx diff --git a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.js b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx similarity index 100% rename from package/src/components/MessageList/__tests__/ScrollToBottomButton.test.js rename to package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx diff --git a/package/src/components/MessageList/__tests__/TypingIndicator.test.js b/package/src/components/MessageList/__tests__/TypingIndicator.test.tsx similarity index 100% rename from package/src/components/MessageList/__tests__/TypingIndicator.test.js rename to package/src/components/MessageList/__tests__/TypingIndicator.test.tsx diff --git a/package/src/components/MessageList/__tests__/__snapshots__/MessageSystem.test.js.snap b/package/src/components/MessageList/__tests__/__snapshots__/MessageSystem.test.tsx.snap similarity index 100% rename from package/src/components/MessageList/__tests__/__snapshots__/MessageSystem.test.js.snap rename to package/src/components/MessageList/__tests__/__snapshots__/MessageSystem.test.tsx.snap diff --git a/package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.js.snap b/package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.tsx.snap similarity index 100% rename from package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.js.snap rename to package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.tsx.snap diff --git a/package/src/components/MessageList/__tests__/__snapshots__/TypingIndicator.test.js.snap b/package/src/components/MessageList/__tests__/__snapshots__/TypingIndicator.test.tsx.snap similarity index 100% rename from package/src/components/MessageList/__tests__/__snapshots__/TypingIndicator.test.js.snap rename to package/src/components/MessageList/__tests__/__snapshots__/TypingIndicator.test.tsx.snap diff --git a/package/src/components/Thread/__tests__/Thread.test.js b/package/src/components/Thread/__tests__/Thread.test.tsx similarity index 100% rename from package/src/components/Thread/__tests__/Thread.test.js rename to package/src/components/Thread/__tests__/Thread.test.tsx diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap similarity index 100% rename from package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap rename to package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap diff --git a/package/src/mock-builders/api/deleteMessage.js b/package/src/mock-builders/api/deleteMessage.js deleted file mode 100644 index a48bb2cb81..0000000000 --- a/package/src/mock-builders/api/deleteMessage.js +++ /dev/null @@ -1,18 +0,0 @@ -import { mockedApiResponse } from './utils'; - -import { generateMessage } from '../generator/message'; -/** - * Returns the api response for sendMessage api. - * - * api - /channels/{type}/{id}/message - * - * @param {*} message - */ -export const deleteMessageApi = (message = generateMessage()) => { - const result = { - duration: 0.01, - message, - }; - - return mockedApiResponse(result, 'delete'); -}; diff --git a/package/src/mock-builders/api/deleteMessage.ts b/package/src/mock-builders/api/deleteMessage.ts new file mode 100644 index 0000000000..2f3324e58d --- /dev/null +++ b/package/src/mock-builders/api/deleteMessage.ts @@ -0,0 +1,21 @@ +import type { MessageResponse } from 'stream-chat'; + +import { mockedApiResponse, type MockedApiResponse } from './utils'; + +import { generateMessage } from '../generator/message'; + +/** + * Returns the api response for deleteMessage api. + * + * api - /channels/{type}/{id}/message + */ +export const deleteMessageApi = ( + message: MessageResponse = generateMessage(), +): MockedApiResponse => { + const result = { + duration: 0.01, + message, + }; + + return mockedApiResponse(result, 'delete'); +}; diff --git a/package/src/mock-builders/api/deleteReaction.js b/package/src/mock-builders/api/deleteReaction.js deleted file mode 100644 index 70ee4bf09e..0000000000 --- a/package/src/mock-builders/api/deleteReaction.js +++ /dev/null @@ -1,19 +0,0 @@ -import { mockedApiResponse } from './utils'; - -import { generateReaction } from '../generator/reaction'; -/** - * Returns the api response for sendMessage api. - * - * api - /messages/{id}/reaction - * - * @param {*} message - */ -export const deleteReactionApi = (message, reaction = generateReaction()) => { - const result = { - duration: 0.01, - message, - reaction, - }; - - return mockedApiResponse(result, 'delete'); -}; diff --git a/package/src/mock-builders/api/deleteReaction.ts b/package/src/mock-builders/api/deleteReaction.ts new file mode 100644 index 0000000000..33d6312a67 --- /dev/null +++ b/package/src/mock-builders/api/deleteReaction.ts @@ -0,0 +1,23 @@ +import type { MessageResponse, ReactionResponse } from 'stream-chat'; + +import { mockedApiResponse, type MockedApiResponse } from './utils'; + +import { generateReaction } from '../generator/reaction'; + +/** + * Returns the api response for deleteReaction api. + * + * api - /messages/{id}/reaction + */ +export const deleteReactionApi = ( + message: MessageResponse, + reaction: ReactionResponse = generateReaction(), +): MockedApiResponse => { + const result = { + duration: 0.01, + message, + reaction, + }; + + return mockedApiResponse(result, 'delete'); +}; diff --git a/package/src/mock-builders/api/error.js b/package/src/mock-builders/api/error.ts similarity index 51% rename from package/src/mock-builders/api/error.js rename to package/src/mock-builders/api/error.ts index 419ce184ba..8fe6e8835e 100644 --- a/package/src/mock-builders/api/error.js +++ b/package/src/mock-builders/api/error.ts @@ -1,4 +1,12 @@ -import { mockedApiResponse } from './utils'; +import { mockedApiResponse, type MockedApiResponse } from './utils'; + +type CustomError = Partial<{ + duration: number; + exception_fields: Record; + message: string; + code: number; + StatusCode: number; +}>; const defaultErrorObject = { duration: 0.01, @@ -6,7 +14,7 @@ const defaultErrorObject = { message: 'API resulted in error', }; -export const erroredGetApi = (customError = {}) => { +export const erroredGetApi = (customError: CustomError = {}): MockedApiResponse => { const error = { ...defaultErrorObject, ...customError, @@ -15,7 +23,7 @@ export const erroredGetApi = (customError = {}) => { return mockedApiResponse(error, 'get', 500); }; -export const erroredPostApi = (customError = {}) => { +export const erroredPostApi = (customError: CustomError = {}): MockedApiResponse => { const error = { ...defaultErrorObject, ...customError, @@ -24,7 +32,7 @@ export const erroredPostApi = (customError = {}) => { return mockedApiResponse(error, 'post', 500); }; -export const erroredPutApi = (customError = {}) => { +export const erroredPutApi = (customError: CustomError = {}): MockedApiResponse => { const error = { ...defaultErrorObject, ...customError, @@ -33,7 +41,7 @@ export const erroredPutApi = (customError = {}) => { return mockedApiResponse(error, 'put', 500); }; -export const erroredDeleteApi = (customError = {}) => { +export const erroredDeleteApi = (customError: CustomError = {}): MockedApiResponse => { const error = { ...defaultErrorObject, ...customError, diff --git a/package/src/mock-builders/api/getOrCreateChannel.ts b/package/src/mock-builders/api/getOrCreateChannel.ts index c88e600897..4648c16f68 100644 --- a/package/src/mock-builders/api/getOrCreateChannel.ts +++ b/package/src/mock-builders/api/getOrCreateChannel.ts @@ -1,21 +1,26 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { mockedApiResponse } from './utils'; +import type { + ChannelMemberResponse, + ChannelResponse, + DraftResponse, + MessageResponse, + ReadResponse, +} from 'stream-chat'; + +import { mockedApiResponse, type MockedApiResponse } from './utils'; export type GetOrCreateChannelApiParams = { - draft?: Record; - channel?: Record; - members?: Record[]; - messages?: Record[]; - pinnedMessages?: Record[]; - read?: Record[]; + draft?: Partial; + channel?: Partial; + members?: Partial[]; + messages?: Partial[]; + pinnedMessages?: Partial[]; + read?: Partial[]; }; /** * Returns the api response for queryChannel api. * * api - /channels/{type}/{id}/query - * - * @param {*} channel */ export const getOrCreateChannelApi = ( channel: GetOrCreateChannelApiParams = { @@ -26,7 +31,7 @@ export const getOrCreateChannelApi = ( pinnedMessages: [], read: [], }, -) => { +): MockedApiResponse => { const result = { channel: channel.channel, draft: channel.draft, diff --git a/package/src/mock-builders/api/initiateClientWithChannels.js b/package/src/mock-builders/api/initiateClientWithChannels.ts similarity index 70% rename from package/src/mock-builders/api/initiateClientWithChannels.js rename to package/src/mock-builders/api/initiateClientWithChannels.ts index e783c012c6..334f239d12 100644 --- a/package/src/mock-builders/api/initiateClientWithChannels.js +++ b/package/src/mock-builders/api/initiateClientWithChannels.ts @@ -1,3 +1,5 @@ +import type { Channel, StreamChat, UserResponse } from 'stream-chat'; + import { getOrCreateChannelApi } from './getOrCreateChannel'; import { useMockedApis } from './useMockedApis'; @@ -6,7 +8,17 @@ import { generateMember } from '../generator/member'; import { generateUser } from '../generator/user'; import { getTestClientWithUser } from '../mock'; -const initChannelFromData = async ({ channelData, client, defaultGenerateChannelOptions }) => { +type ChannelData = Record; + +const initChannelFromData = async ({ + channelData, + client, + defaultGenerateChannelOptions, +}: { + channelData: ChannelData; + client: StreamChat; + defaultGenerateChannelOptions: ChannelData; +}): Promise => { const mockedChannelData = generateChannel({ ...defaultGenerateChannelOptions, ...channelData, @@ -22,7 +34,13 @@ const initChannelFromData = async ({ channelData, client, defaultGenerateChannel return channel; }; -export const initiateClientWithChannels = async ({ channelsData, customUser } = {}) => { +export const initiateClientWithChannels = async ({ + channelsData, + customUser, +}: { + channelsData?: ChannelData[]; + customUser?: UserResponse; +} = {}): Promise<{ channels: Channel[]; client: StreamChat }> => { const user = customUser || generateUser(); const client = await getTestClientWithUser(user); diff --git a/package/src/mock-builders/api/queryChannels.js b/package/src/mock-builders/api/queryChannels.ts similarity index 55% rename from package/src/mock-builders/api/queryChannels.js rename to package/src/mock-builders/api/queryChannels.ts index 3c27043319..645db73f9a 100644 --- a/package/src/mock-builders/api/queryChannels.js +++ b/package/src/mock-builders/api/queryChannels.ts @@ -1,13 +1,11 @@ -import { mockedApiResponse } from './utils'; +import { mockedApiResponse, type MockedApiResponse } from './utils'; /** * Returns the api response for queryChannels api * * api - /channels - * - * @param {*} channels Array of channel objects. */ -export const queryChannelsApi = (channels = []) => { +export const queryChannelsApi = (channels: unknown[] = []): MockedApiResponse => { const result = { channels, duration: 0.01, diff --git a/package/src/mock-builders/api/queryMembers.js b/package/src/mock-builders/api/queryMembers.ts similarity index 65% rename from package/src/mock-builders/api/queryMembers.js rename to package/src/mock-builders/api/queryMembers.ts index e0bc27c003..2afa093220 100644 --- a/package/src/mock-builders/api/queryMembers.js +++ b/package/src/mock-builders/api/queryMembers.ts @@ -1,13 +1,14 @@ -import { mockedApiResponse } from './utils'; +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelMemberResponse } from 'stream-chat'; + +import { mockedApiResponse, type MockedApiResponse } from './utils'; /** * Returns the api response for queryMembers api * * api - /query_members - * - * @param {*} members Array of User objects. */ -export const queryMembersApi = (members = []) => { +export const queryMembersApi = (members: ChannelMemberResponse[] = []): MockedApiResponse => { const result = { members, }; @@ -15,8 +16,8 @@ export const queryMembersApi = (members = []) => { return mockedApiResponse(result, 'get'); }; -export const CHANNEL_MEMBERS = [ - { +export const CHANNEL_MEMBERS: ChannelMemberResponse[] = [ + fromPartial({ banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -28,8 +29,8 @@ export const CHANNEL_MEMBERS = [ name: 'ben', }, user_id: 'ben', - }, - { + }), + fromPartial({ banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -41,8 +42,8 @@ export const CHANNEL_MEMBERS = [ name: 'nick', }, user_id: 'nick', - }, - { + }), + fromPartial({ banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -54,8 +55,8 @@ export const CHANNEL_MEMBERS = [ name: 'okechukwu nwagba', }, user_id: 'okechukwu nwagba', - }, - { + }), + fromPartial({ banned: false, channel_role: 'channel_member', created_at: '2021-01-28T09:08:43.274508Z', @@ -67,9 +68,9 @@ export const CHANNEL_MEMBERS = [ name: 'qatest1', }, user_id: 'qatest1', - }, + }), - { + fromPartial({ banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -81,11 +82,11 @@ export const CHANNEL_MEMBERS = [ name: 'thierry', }, user_id: 'thierry', - }, + }), ]; -export const ONE_CHANNEL_MEMBER = [ - { +export const ONE_CHANNEL_MEMBER: ChannelMemberResponse[] = [ + fromPartial({ banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -97,20 +98,21 @@ export const ONE_CHANNEL_MEMBER = [ name: 'okechukwu nwagba martin', }, user_id: 'okechukwu nwagba martin', - }, + }), ]; -export const ONE_CHANNEL_MEMBER_MOCK = { +export const ONE_CHANNEL_MEMBER_MOCK: Record = { okey: ONE_CHANNEL_MEMBER[0], }; -export const GROUP_CHANNEL_MEMBERS_MOCK = CHANNEL_MEMBERS.reduce((acc, member) => { - acc[member.user_id] = member; - return acc; -}, {}); +export const GROUP_CHANNEL_MEMBERS_MOCK: Record = + CHANNEL_MEMBERS.reduce>((acc, member) => { + if (member.user_id) acc[member.user_id] = member; + return acc; + }, {}); -export const ONE_MEMBER_WITH_EMPTY_USER = [ - { +export const ONE_MEMBER_WITH_EMPTY_USER: ChannelMemberResponse[] = [ + fromPartial({ banned: false, channel_role: 'channel_member', created_at: '2021-01-27T11:54:34.173125Z', @@ -119,9 +121,9 @@ export const ONE_MEMBER_WITH_EMPTY_USER = [ updated_at: '2021-02-12T12:12:35.862282Z', user: {}, user_id: 'okechukwu nwagba martin', - }, + }), ]; -export const ONE_MEMBER_WITH_EMPTY_USER_MOCK = { +export const ONE_MEMBER_WITH_EMPTY_USER_MOCK: Record = { okey: ONE_MEMBER_WITH_EMPTY_USER[0], }; diff --git a/package/src/mock-builders/api/sendMessage.js b/package/src/mock-builders/api/sendMessage.ts similarity index 54% rename from package/src/mock-builders/api/sendMessage.js rename to package/src/mock-builders/api/sendMessage.ts index c704811c5d..0299faa64c 100644 --- a/package/src/mock-builders/api/sendMessage.js +++ b/package/src/mock-builders/api/sendMessage.ts @@ -1,14 +1,15 @@ -import { mockedApiResponse } from './utils'; +import type { MessageResponse } from 'stream-chat'; + +import { mockedApiResponse, type MockedApiResponse } from './utils'; import { generateMessage } from '../generator/message'; + /** * Returns the api response for sendMessage api. * * api - /channels/{type}/{id}/message - * - * @param {*} message */ -export const sendMessageApi = (message = generateMessage()) => { +export const sendMessageApi = (message: MessageResponse = generateMessage()): MockedApiResponse => { const result = { duration: 0.01, message, diff --git a/package/src/mock-builders/api/sendReaction.ts b/package/src/mock-builders/api/sendReaction.ts index 51bb5f1e82..7690688f6a 100644 --- a/package/src/mock-builders/api/sendReaction.ts +++ b/package/src/mock-builders/api/sendReaction.ts @@ -1,14 +1,18 @@ -import { mockedApiResponse } from './utils'; +import type { MessageResponse, ReactionResponse } from 'stream-chat'; + +import { mockedApiResponse, type MockedApiResponse } from './utils'; import { generateReaction } from '../generator/reaction'; + /** - * Returns the api response for sendMessage api. + * Returns the api response for sendReaction api. * * api - /messages/{id}/reaction - * - * @param {*} message */ -export const sendReactionApi = (message, reaction = generateReaction()) => { +export const sendReactionApi = ( + message: MessageResponse, + reaction: ReactionResponse = generateReaction(), +): MockedApiResponse => { const result = { duration: 0.01, message, diff --git a/package/src/mock-builders/api/threadReplies.js b/package/src/mock-builders/api/threadReplies.js deleted file mode 100644 index 2c88511f17..0000000000 --- a/package/src/mock-builders/api/threadReplies.js +++ /dev/null @@ -1,16 +0,0 @@ -import { mockedApiResponse } from './utils'; - -/** - * Returns the api response for thread replies api - * - * api - /messages/${parent_id}/replies - * - * @param {*} replies Array of message objects. - */ -export const threadRepliesApi = (replies = []) => { - const result = { - messages: replies, - }; - - return mockedApiResponse(result, 'get'); -}; diff --git a/package/src/mock-builders/api/threadReplies.ts b/package/src/mock-builders/api/threadReplies.ts new file mode 100644 index 0000000000..8ec8e0c5d9 --- /dev/null +++ b/package/src/mock-builders/api/threadReplies.ts @@ -0,0 +1,16 @@ +import type { MessageResponse } from 'stream-chat'; + +import { mockedApiResponse, type MockedApiResponse } from './utils'; + +/** + * Returns the api response for thread replies api + * + * api - /messages/${parent_id}/replies + */ +export const threadRepliesApi = (replies: MessageResponse[] = []): MockedApiResponse => { + const result = { + messages: replies, + }; + + return mockedApiResponse(result, 'get'); +}; diff --git a/package/src/mock-builders/api/useMockedApis.js b/package/src/mock-builders/api/useMockedApis.ts similarity index 68% rename from package/src/mock-builders/api/useMockedApis.js rename to package/src/mock-builders/api/useMockedApis.ts index 1fb9a720d0..31865f954b 100644 --- a/package/src/mock-builders/api/useMockedApis.js +++ b/package/src/mock-builders/api/useMockedApis.ts @@ -1,13 +1,14 @@ +import type { StreamChat } from 'stream-chat'; + +import type { MockedApiResponse } from './utils'; + /** * Hook to mock the calls made through axios module. * You should provide the responses of Apis in order that they will be called. * You should use api functions from current directory to build these responses. * e.g., queryChannelsApi, sendMessageApi - * - * @param {StreamClient} client - * @param {*} apiResponses */ -export const useMockedApis = (client, apiResponses) => { +export const useMockedApis = (client: StreamChat, apiResponses: MockedApiResponse[]) => { apiResponses.forEach(({ response, type }) => { jest.spyOn(client.axiosInstance, type).mockImplementation().mockResolvedValue(response); }); diff --git a/package/src/mock-builders/api/utils.js b/package/src/mock-builders/api/utils.js deleted file mode 100644 index 34df5e61e4..0000000000 --- a/package/src/mock-builders/api/utils.js +++ /dev/null @@ -1,7 +0,0 @@ -export const mockedApiResponse = (response, type = 'get', status = 200) => ({ - response: { - data: response, - status, - }, - type, -}); diff --git a/package/src/mock-builders/api/utils.ts b/package/src/mock-builders/api/utils.ts new file mode 100644 index 0000000000..81080b672b --- /dev/null +++ b/package/src/mock-builders/api/utils.ts @@ -0,0 +1,16 @@ +export type MockedApiResponse = { + response: { data: unknown; status: number }; + type: 'get' | 'post' | 'put' | 'delete'; +}; + +export const mockedApiResponse = ( + response: unknown, + type: MockedApiResponse['type'] = 'get', + status = 200, +): MockedApiResponse => ({ + response: { + data: response, + status, + }, + type, +}); diff --git a/package/src/mock-builders/attachments.js b/package/src/mock-builders/attachments.ts similarity index 51% rename from package/src/mock-builders/attachments.js rename to package/src/mock-builders/attachments.ts index 19de51d537..b5eceab7c1 100644 --- a/package/src/mock-builders/attachments.js +++ b/package/src/mock-builders/attachments.ts @@ -1,43 +1,59 @@ +import type { Attachment } from 'stream-chat'; + import { generateRandomId } from '../utils/utils'; -export const generateLocalAttachmentData = () => ({ +type FileReference = { + name: string; + size: number; + type: string; + uri: string; +}; + +type LocalAttachmentData = { + localMetadata: { id: string }; +}; + +export const generateLocalAttachmentData = (): LocalAttachmentData => ({ localMetadata: { id: generateRandomId(), }, }); -export const generateLocalFileUploadAttachmentData = (overrides, attachmentData) => ({ +export const generateLocalFileUploadAttachmentData = ( + overrides?: Partial<{ file: Partial } & Record>, + attachmentData?: Partial, +) => ({ localMetadata: { ...generateLocalAttachmentData().localMetadata, ...overrides, file: generateFileReference(overrides?.file ?? {}), }, - type: 'file', + type: 'file' as const, ...attachmentData, }); -export const generateImageAttachment = (a) => ({ +export const generateImageAttachment = (a?: Partial): Attachment => ({ fallback: generateRandomId() + '.png', image_url: 'https://' + generateRandomId() + '.png', type: 'image', ...a, }); -export const generateAudioAttachment = (a) => ({ +export const generateAudioAttachment = (a?: Partial): Attachment => ({ asset_url: 'https://' + generateRandomId() + '.mp3', fallback: generateRandomId() + '.mp3', type: 'audio', ...a, }); -export const generateFileAttachment = (a) => ({ +export const generateFileAttachment = (a?: Partial): Attachment => ({ asset_url: 'https://' + generateRandomId() + '.xls', fallback: generateRandomId() + '.xls', type: 'file', ...a, }); -export const generateVideoAttachment = (a) => ({ +export const generateVideoAttachment = (a?: Partial): Attachment => ({ fallback: generateRandomId() + '.mp4', image_url: 'https://' + generateRandomId() + '.mp4', type: 'video', @@ -46,7 +62,7 @@ export const generateVideoAttachment = (a) => ({ const fileName = generateRandomId() + '.png'; -export const generateFileReference = (a) => ({ +export const generateFileReference = (a?: Partial): FileReference => ({ name: fileName, size: 1000, type: 'image/png', diff --git a/package/src/mock-builders/event/channelDeleted.js b/package/src/mock-builders/event/channelDeleted.js deleted file mode 100644 index 3b05536541..0000000000 --- a/package/src/mock-builders/event/channelDeleted.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'channel.deleted', - }); -}; diff --git a/package/src/mock-builders/event/channelDeleted.ts b/package/src/mock-builders/event/channelDeleted.ts new file mode 100644 index 0000000000..21576a8627 --- /dev/null +++ b/package/src/mock-builders/event/channelDeleted.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'channel.deleted', + }), + ); +}; diff --git a/package/src/mock-builders/event/channelHidden.js b/package/src/mock-builders/event/channelHidden.js deleted file mode 100644 index 6c144f89ec..0000000000 --- a/package/src/mock-builders/event/channelHidden.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'channel.hidden', - }); -}; diff --git a/package/src/mock-builders/event/channelHidden.ts b/package/src/mock-builders/event/channelHidden.ts new file mode 100644 index 0000000000..4d30eae961 --- /dev/null +++ b/package/src/mock-builders/event/channelHidden.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'channel.hidden', + }), + ); +}; diff --git a/package/src/mock-builders/event/channelTruncated.js b/package/src/mock-builders/event/channelTruncated.js deleted file mode 100644 index 7bffbd47b2..0000000000 --- a/package/src/mock-builders/event/channelTruncated.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'channel.truncated', - }); -}; diff --git a/package/src/mock-builders/event/channelTruncated.ts b/package/src/mock-builders/event/channelTruncated.ts new file mode 100644 index 0000000000..b10e1c2676 --- /dev/null +++ b/package/src/mock-builders/event/channelTruncated.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'channel.truncated', + }), + ); +}; diff --git a/package/src/mock-builders/event/channelUpdated.js b/package/src/mock-builders/event/channelUpdated.js deleted file mode 100644 index 099e10804e..0000000000 --- a/package/src/mock-builders/event/channelUpdated.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'channel.updated', - }); -}; diff --git a/package/src/mock-builders/event/channelUpdated.ts b/package/src/mock-builders/event/channelUpdated.ts new file mode 100644 index 0000000000..559dbb9d65 --- /dev/null +++ b/package/src/mock-builders/event/channelUpdated.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'channel.updated', + }), + ); +}; diff --git a/package/src/mock-builders/event/channelVisible.js b/package/src/mock-builders/event/channelVisible.js deleted file mode 100644 index c74df7eed3..0000000000 --- a/package/src/mock-builders/event/channelVisible.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'channel.visible', - }); -}; diff --git a/package/src/mock-builders/event/channelVisible.ts b/package/src/mock-builders/event/channelVisible.ts new file mode 100644 index 0000000000..42f20fc350 --- /dev/null +++ b/package/src/mock-builders/event/channelVisible.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'channel.visible', + }), + ); +}; diff --git a/package/src/mock-builders/event/connectionChanged.js b/package/src/mock-builders/event/connectionChanged.js deleted file mode 100644 index adb1314180..0000000000 --- a/package/src/mock-builders/event/connectionChanged.js +++ /dev/null @@ -1,6 +0,0 @@ -export default (client, online = true) => { - client.dispatchEvent({ - online, - type: 'connection.changed', - }); -}; diff --git a/package/src/mock-builders/event/connectionChanged.ts b/package/src/mock-builders/event/connectionChanged.ts new file mode 100644 index 0000000000..158310158f --- /dev/null +++ b/package/src/mock-builders/event/connectionChanged.ts @@ -0,0 +1,11 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, online = true) => { + client.dispatchEvent( + fromPartial({ + online, + type: 'connection.changed', + }), + ); +}; diff --git a/package/src/mock-builders/event/connectionRecovered.js b/package/src/mock-builders/event/connectionRecovered.js deleted file mode 100644 index e47a21833a..0000000000 --- a/package/src/mock-builders/event/connectionRecovered.js +++ /dev/null @@ -1,5 +0,0 @@ -export default (client) => { - client.dispatchEvent({ - type: 'connection.recovered', - }); -}; diff --git a/package/src/mock-builders/event/connectionRecovered.ts b/package/src/mock-builders/event/connectionRecovered.ts new file mode 100644 index 0000000000..a311ff7b64 --- /dev/null +++ b/package/src/mock-builders/event/connectionRecovered.ts @@ -0,0 +1,10 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat) => { + client.dispatchEvent( + fromPartial({ + type: 'connection.recovered', + }), + ); +}; diff --git a/package/src/mock-builders/event/memberAdded.js b/package/src/mock-builders/event/memberAdded.js deleted file mode 100644 index b9281f98ef..0000000000 --- a/package/src/mock-builders/event/memberAdded.js +++ /dev/null @@ -1,10 +0,0 @@ -export default (client, member, channel = {}) => { - client.dispatchEvent({ - channel_id: channel.id, - channel_type: channel.type, - cid: channel.cid, - member, - type: 'member.added', - user: member.user, - }); -}; diff --git a/package/src/mock-builders/event/memberAdded.ts b/package/src/mock-builders/event/memberAdded.ts new file mode 100644 index 0000000000..bb9c8eb3ee --- /dev/null +++ b/package/src/mock-builders/event/memberAdded.ts @@ -0,0 +1,19 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelMemberResponse, ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default ( + client: StreamChat, + member: ChannelMemberResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel_id: channel.id, + channel_type: channel.type, + cid: channel.cid, + member, + type: 'member.added', + user: member.user, + }), + ); +}; diff --git a/package/src/mock-builders/event/memberRemoved.js b/package/src/mock-builders/event/memberRemoved.js deleted file mode 100644 index 174f7758c0..0000000000 --- a/package/src/mock-builders/event/memberRemoved.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (client, member, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - member, - type: 'member.removed', - user: member.user, - }); -}; diff --git a/package/src/mock-builders/event/memberRemoved.ts b/package/src/mock-builders/event/memberRemoved.ts new file mode 100644 index 0000000000..ed9f3d181a --- /dev/null +++ b/package/src/mock-builders/event/memberRemoved.ts @@ -0,0 +1,18 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelMemberResponse, ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default ( + client: StreamChat, + member: ChannelMemberResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + member, + type: 'member.removed', + user: member.user, + }), + ); +}; diff --git a/package/src/mock-builders/event/memberUpdated.js b/package/src/mock-builders/event/memberUpdated.js deleted file mode 100644 index a337633f57..0000000000 --- a/package/src/mock-builders/event/memberUpdated.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (client, member, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - member, - type: 'member.updated', - user: member.user, - }); -}; diff --git a/package/src/mock-builders/event/memberUpdated.ts b/package/src/mock-builders/event/memberUpdated.ts new file mode 100644 index 0000000000..40837f31a2 --- /dev/null +++ b/package/src/mock-builders/event/memberUpdated.ts @@ -0,0 +1,18 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelMemberResponse, ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default ( + client: StreamChat, + member: ChannelMemberResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + member, + type: 'member.updated', + user: member.user, + }), + ); +}; diff --git a/package/src/mock-builders/event/messageDeleted.js b/package/src/mock-builders/event/messageDeleted.js deleted file mode 100644 index 27f5482740..0000000000 --- a/package/src/mock-builders/event/messageDeleted.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (client, message, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - message, - type: 'message.deleted', - }); -}; diff --git a/package/src/mock-builders/event/messageDeleted.ts b/package/src/mock-builders/event/messageDeleted.ts new file mode 100644 index 0000000000..519905371d --- /dev/null +++ b/package/src/mock-builders/event/messageDeleted.ts @@ -0,0 +1,17 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, MessageResponse, StreamChat } from 'stream-chat'; + +export default ( + client: StreamChat, + message: MessageResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + message, + type: 'message.deleted', + }), + ); +}; diff --git a/package/src/mock-builders/event/messageNew.js b/package/src/mock-builders/event/messageNew.js deleted file mode 100644 index 0453a41d52..0000000000 --- a/package/src/mock-builders/event/messageNew.js +++ /dev/null @@ -1,11 +0,0 @@ -export default (client, newMessage, channel = {}) => { - client.dispatchEvent({ - channel, - channel_id: channel.id, - channel_type: channel.type, - cid: channel.cid, - message: newMessage, - type: 'message.new', - ...(newMessage.user ? { user: newMessage.user } : {}), - }); -}; diff --git a/package/src/mock-builders/event/messageNew.ts b/package/src/mock-builders/event/messageNew.ts new file mode 100644 index 0000000000..54178b656a --- /dev/null +++ b/package/src/mock-builders/event/messageNew.ts @@ -0,0 +1,20 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, MessageResponse, StreamChat } from 'stream-chat'; + +export default ( + client: StreamChat, + newMessage: MessageResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + channel_id: channel.id, + channel_type: channel.type, + cid: channel.cid, + message: newMessage, + type: 'message.new', + ...(newMessage.user ? { user: newMessage.user } : {}), + }), + ); +}; diff --git a/package/src/mock-builders/event/messageRead.js b/package/src/mock-builders/event/messageRead.js deleted file mode 100644 index 9edbab30f2..0000000000 --- a/package/src/mock-builders/event/messageRead.js +++ /dev/null @@ -1,15 +0,0 @@ -export default (client, user, channel = {}, payload = {}) => { - const newDate = new Date(); - const event = { - channel, - cid: channel.cid, - created_at: newDate, - received_at: newDate, - type: 'message.read', - user, - ...payload, - }; - client.dispatchEvent(event); - - return event; -}; diff --git a/package/src/mock-builders/event/messageRead.ts b/package/src/mock-builders/event/messageRead.ts new file mode 100644 index 0000000000..00db7f92c5 --- /dev/null +++ b/package/src/mock-builders/event/messageRead.ts @@ -0,0 +1,23 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat, UserResponse } from 'stream-chat'; + +export default ( + client: StreamChat, + user: UserResponse, + channel: Partial = {}, + payload: Partial = {}, +): Event => { + const newDate = new Date(); + const event = fromPartial({ + channel, + cid: channel.cid, + created_at: newDate, + received_at: newDate, + type: 'message.read', + user, + ...payload, + }); + client.dispatchEvent(event); + + return event; +}; diff --git a/package/src/mock-builders/event/messageUpdated.js b/package/src/mock-builders/event/messageUpdated.js deleted file mode 100644 index 93fb81e01d..0000000000 --- a/package/src/mock-builders/event/messageUpdated.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (client, newMessage, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - message: newMessage, - type: 'message.updated', - }); -}; diff --git a/package/src/mock-builders/event/messageUpdated.ts b/package/src/mock-builders/event/messageUpdated.ts new file mode 100644 index 0000000000..127ed677eb --- /dev/null +++ b/package/src/mock-builders/event/messageUpdated.ts @@ -0,0 +1,17 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, MessageResponse, StreamChat } from 'stream-chat'; + +export default ( + client: StreamChat, + newMessage: MessageResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + message: newMessage, + type: 'message.updated', + }), + ); +}; diff --git a/package/src/mock-builders/event/notificationAddedToChannel.js b/package/src/mock-builders/event/notificationAddedToChannel.js deleted file mode 100644 index 941a1fef63..0000000000 --- a/package/src/mock-builders/event/notificationAddedToChannel.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'notification.added_to_channel', - }); -}; diff --git a/package/src/mock-builders/event/notificationAddedToChannel.ts b/package/src/mock-builders/event/notificationAddedToChannel.ts new file mode 100644 index 0000000000..d9e7c8c843 --- /dev/null +++ b/package/src/mock-builders/event/notificationAddedToChannel.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'notification.added_to_channel', + }), + ); +}; diff --git a/package/src/mock-builders/event/notificationChannelMutesUpdated.js b/package/src/mock-builders/event/notificationChannelMutesUpdated.js deleted file mode 100644 index 3600092681..0000000000 --- a/package/src/mock-builders/event/notificationChannelMutesUpdated.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'notification.channel_mutes_updated', - }); -}; diff --git a/package/src/mock-builders/event/notificationChannelMutesUpdated.ts b/package/src/mock-builders/event/notificationChannelMutesUpdated.ts new file mode 100644 index 0000000000..100e41e310 --- /dev/null +++ b/package/src/mock-builders/event/notificationChannelMutesUpdated.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'notification.channel_mutes_updated', + }), + ); +}; diff --git a/package/src/mock-builders/event/notificationMarkRead.js b/package/src/mock-builders/event/notificationMarkRead.js deleted file mode 100644 index 8978706f8f..0000000000 --- a/package/src/mock-builders/event/notificationMarkRead.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'notification.mark_read', - }); -}; diff --git a/package/src/mock-builders/event/notificationMarkRead.ts b/package/src/mock-builders/event/notificationMarkRead.ts new file mode 100644 index 0000000000..0158f1673c --- /dev/null +++ b/package/src/mock-builders/event/notificationMarkRead.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'notification.mark_read', + }), + ); +}; diff --git a/package/src/mock-builders/event/notificationMarkUnread.js b/package/src/mock-builders/event/notificationMarkUnread.js deleted file mode 100644 index 50dd0255c7..0000000000 --- a/package/src/mock-builders/event/notificationMarkUnread.js +++ /dev/null @@ -1,12 +0,0 @@ -export default (client, channel = {}, payload = {}, user = {}) => { - const newDate = new Date(); - client.dispatchEvent({ - channel, - cid: channel.cid, - created_at: newDate, - received_at: newDate, - type: 'notification.mark_unread', - user, - ...payload, - }); -}; diff --git a/package/src/mock-builders/event/notificationMarkUnread.ts b/package/src/mock-builders/event/notificationMarkUnread.ts new file mode 100644 index 0000000000..424da94ea4 --- /dev/null +++ b/package/src/mock-builders/event/notificationMarkUnread.ts @@ -0,0 +1,22 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat, UserResponse } from 'stream-chat'; + +export default ( + client: StreamChat, + channel: Partial = {}, + payload: Partial = {}, + user: Partial = {}, +) => { + const newDate = new Date(); + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + created_at: newDate, + received_at: newDate, + type: 'notification.mark_unread', + user, + ...payload, + }), + ); +}; diff --git a/package/src/mock-builders/event/notificationMessageNew.js b/package/src/mock-builders/event/notificationMessageNew.js deleted file mode 100644 index 6ffeb2bba5..0000000000 --- a/package/src/mock-builders/event/notificationMessageNew.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'notification.message_new', - }); -}; diff --git a/package/src/mock-builders/event/notificationMessageNew.ts b/package/src/mock-builders/event/notificationMessageNew.ts new file mode 100644 index 0000000000..4011148b92 --- /dev/null +++ b/package/src/mock-builders/event/notificationMessageNew.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'notification.message_new', + }), + ); +}; diff --git a/package/src/mock-builders/event/notificationMutesUpdated.js b/package/src/mock-builders/event/notificationMutesUpdated.js deleted file mode 100644 index 3f69522848..0000000000 --- a/package/src/mock-builders/event/notificationMutesUpdated.js +++ /dev/null @@ -1,11 +0,0 @@ -export default (client, mutes = []) => { - client.dispatchEvent({ - created_at: '2020-05-26T07:11:57.968294216Z', - me: { - ...client.user, - channel_mutes: [], - mutes, - }, - type: 'notification.mutes_updated', - }); -}; diff --git a/package/src/mock-builders/event/notificationMutesUpdated.ts b/package/src/mock-builders/event/notificationMutesUpdated.ts new file mode 100644 index 0000000000..f3a331cf9b --- /dev/null +++ b/package/src/mock-builders/event/notificationMutesUpdated.ts @@ -0,0 +1,16 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { Event, Mute, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, mutes: Mute[] = []) => { + client.dispatchEvent( + fromPartial({ + created_at: '2020-05-26T07:11:57.968294216Z', + me: { + ...client.user, + channel_mutes: [], + mutes, + }, + type: 'notification.mutes_updated', + }), + ); +}; diff --git a/package/src/mock-builders/event/notificationRemovedFromChannel.js b/package/src/mock-builders/event/notificationRemovedFromChannel.js deleted file mode 100644 index 634c7d5a7a..0000000000 --- a/package/src/mock-builders/event/notificationRemovedFromChannel.js +++ /dev/null @@ -1,7 +0,0 @@ -export default (client, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'notification.removed_from_channel', - }); -}; diff --git a/package/src/mock-builders/event/notificationRemovedFromChannel.ts b/package/src/mock-builders/event/notificationRemovedFromChannel.ts new file mode 100644 index 0000000000..739e7fb978 --- /dev/null +++ b/package/src/mock-builders/event/notificationRemovedFromChannel.ts @@ -0,0 +1,12 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat } from 'stream-chat'; + +export default (client: StreamChat, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'notification.removed_from_channel', + }), + ); +}; diff --git a/package/src/mock-builders/event/reactionDeleted.js b/package/src/mock-builders/event/reactionDeleted.js deleted file mode 100644 index b7c222d654..0000000000 --- a/package/src/mock-builders/event/reactionDeleted.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (client, reaction, message, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - message, - reaction, - type: 'reaction.deleted', - }); -}; diff --git a/package/src/mock-builders/event/reactionDeleted.ts b/package/src/mock-builders/event/reactionDeleted.ts new file mode 100644 index 0000000000..5598ddb72a --- /dev/null +++ b/package/src/mock-builders/event/reactionDeleted.ts @@ -0,0 +1,25 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { + ChannelResponse, + Event, + MessageResponse, + ReactionResponse, + StreamChat, +} from 'stream-chat'; + +export default ( + client: StreamChat, + reaction: ReactionResponse, + message: MessageResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + message, + reaction, + type: 'reaction.deleted', + }), + ); +}; diff --git a/package/src/mock-builders/event/reactionNew.js b/package/src/mock-builders/event/reactionNew.js deleted file mode 100644 index efddf9468f..0000000000 --- a/package/src/mock-builders/event/reactionNew.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (client, reaction, message, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - message, - reaction, - type: 'reaction.new', - }); -}; diff --git a/package/src/mock-builders/event/reactionNew.ts b/package/src/mock-builders/event/reactionNew.ts new file mode 100644 index 0000000000..75f4708afc --- /dev/null +++ b/package/src/mock-builders/event/reactionNew.ts @@ -0,0 +1,25 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { + ChannelResponse, + Event, + MessageResponse, + ReactionResponse, + StreamChat, +} from 'stream-chat'; + +export default ( + client: StreamChat, + reaction: ReactionResponse, + message: MessageResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + message, + reaction, + type: 'reaction.new', + }), + ); +}; diff --git a/package/src/mock-builders/event/reactionUpdated.js b/package/src/mock-builders/event/reactionUpdated.js deleted file mode 100644 index 26b01e13fc..0000000000 --- a/package/src/mock-builders/event/reactionUpdated.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (client, reaction, message, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - message, - reaction, - type: 'reaction.updated', - }); -}; diff --git a/package/src/mock-builders/event/reactionUpdated.ts b/package/src/mock-builders/event/reactionUpdated.ts new file mode 100644 index 0000000000..8e6ef9d339 --- /dev/null +++ b/package/src/mock-builders/event/reactionUpdated.ts @@ -0,0 +1,25 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { + ChannelResponse, + Event, + MessageResponse, + ReactionResponse, + StreamChat, +} from 'stream-chat'; + +export default ( + client: StreamChat, + reaction: ReactionResponse, + message: MessageResponse, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + message, + reaction, + type: 'reaction.updated', + }), + ); +}; diff --git a/package/src/mock-builders/event/typing.js b/package/src/mock-builders/event/typing.js deleted file mode 100644 index 72d6b0d215..0000000000 --- a/package/src/mock-builders/event/typing.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (client, user = {}, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'typing.start', - user, - user_id: user.id, - }); -}; diff --git a/package/src/mock-builders/event/typing.ts b/package/src/mock-builders/event/typing.ts new file mode 100644 index 0000000000..efe175e1e2 --- /dev/null +++ b/package/src/mock-builders/event/typing.ts @@ -0,0 +1,18 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat, UserResponse } from 'stream-chat'; + +export default ( + client: StreamChat, + user: Partial = {}, + channel: Partial = {}, +) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'typing.start', + user, + user_id: user.id, + }), + ); +}; diff --git a/package/src/mock-builders/event/userPresence.js b/package/src/mock-builders/event/userPresence.js deleted file mode 100644 index d747b6f30e..0000000000 --- a/package/src/mock-builders/event/userPresence.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (client, user, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'user.presence.changed', - user, - }); -}; diff --git a/package/src/mock-builders/event/userPresence.ts b/package/src/mock-builders/event/userPresence.ts new file mode 100644 index 0000000000..a6c5d838a1 --- /dev/null +++ b/package/src/mock-builders/event/userPresence.ts @@ -0,0 +1,13 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat, UserResponse } from 'stream-chat'; + +export default (client: StreamChat, user: UserResponse, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'user.presence.changed', + user, + }), + ); +}; diff --git a/package/src/mock-builders/event/userUpdated.js b/package/src/mock-builders/event/userUpdated.js deleted file mode 100644 index bf3cbc5918..0000000000 --- a/package/src/mock-builders/event/userUpdated.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (client, user, channel = {}) => { - client.dispatchEvent({ - channel, - cid: channel.cid, - type: 'user.updated', - user, - }); -}; diff --git a/package/src/mock-builders/event/userUpdated.ts b/package/src/mock-builders/event/userUpdated.ts new file mode 100644 index 0000000000..2f0c16c9d0 --- /dev/null +++ b/package/src/mock-builders/event/userUpdated.ts @@ -0,0 +1,13 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelResponse, Event, StreamChat, UserResponse } from 'stream-chat'; + +export default (client: StreamChat, user: UserResponse, channel: Partial = {}) => { + client.dispatchEvent( + fromPartial({ + channel, + cid: channel.cid, + type: 'user.updated', + user, + }), + ); +}; diff --git a/package/src/mock-builders/generator/attachment.js b/package/src/mock-builders/generator/attachment.ts similarity index 54% rename from package/src/mock-builders/generator/attachment.js rename to package/src/mock-builders/generator/attachment.ts index 273cdafb76..c55c03ca7c 100644 --- a/package/src/mock-builders/generator/attachment.js +++ b/package/src/mock-builders/generator/attachment.ts @@ -1,15 +1,16 @@ +import type { Action, Attachment } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; const image_url = 'http://www.jackblack.com/tenac_iousd.bmp'; -export const generateAttachmentAction = (a) => ({ +export const generateAttachmentAction = (a?: Partial): Action => ({ name: uuidv4(), text: uuidv4(), value: uuidv4(), ...a, }); -export const generateVideoAttachment = (a) => ({ +export const generateVideoAttachment = (a?: Partial): Attachment => ({ asset_url: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', mime_type: 'video/mp4', thumb_url: @@ -19,7 +20,7 @@ export const generateVideoAttachment = (a) => ({ ...a, }); -export const generateImageAttachment = (a) => ({ +export const generateImageAttachment = (a?: Partial): Attachment => ({ id: uuidv4(), image_url: uuidv4(), title: uuidv4(), @@ -27,7 +28,13 @@ export const generateImageAttachment = (a) => ({ ...a, }); -export const generateImageUploadPreview = (a) => ({ +type UploadPreview = { + file: { uri?: string; name?: string; type?: string }; + id: string; + state: string; +}; + +export const generateImageUploadPreview = (a?: Partial): UploadPreview => ({ file: { uri: image_url, }, @@ -36,7 +43,7 @@ export const generateImageUploadPreview = (a) => ({ ...a, }); -export const generateAudioAttachment = (a) => ({ +export const generateAudioAttachment = (a?: Partial): Attachment => ({ asset_url: 'http://www.jackblack.com/tribute.mp3', description: uuidv4(), image_url, @@ -46,7 +53,7 @@ export const generateAudioAttachment = (a) => ({ ...a, }); -export const generateFileAttachment = (a) => ({ +export const generateFileAttachment = (a?: Partial): Attachment => ({ asset_url: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', description: uuidv4(), file_size: 1337, @@ -57,7 +64,7 @@ export const generateFileAttachment = (a) => ({ ...a, }); -export const generateFileUploadPreview = (a) => ({ +export const generateFileUploadPreview = (a?: Partial): UploadPreview => ({ file: { name: 'dummy.pdf', type: 'file', @@ -68,7 +75,7 @@ export const generateFileUploadPreview = (a) => ({ ...a, }); -export const generateCardAttachment = (a) => ({ +export const generateCardAttachment = (a?: Partial): Attachment => ({ image_url, og_scrape_url: uuidv4(), text: uuidv4(), @@ -78,6 +85,6 @@ export const generateCardAttachment = (a) => ({ ...a, }); -export const generateImgurAttachment = () => generateCardAttachment({ type: 'imgur' }); +export const generateImgurAttachment = (): Attachment => generateCardAttachment({ type: 'imgur' }); -export const generateGiphyAttachment = () => generateCardAttachment({ type: 'giphy' }); +export const generateGiphyAttachment = (): Attachment => generateCardAttachment({ type: 'giphy' }); diff --git a/package/src/mock-builders/generator/channel.ts b/package/src/mock-builders/generator/channel.ts index 8b0efad2ad..3a254c49b8 100644 --- a/package/src/mock-builders/generator/channel.ts +++ b/package/src/mock-builders/generator/channel.ts @@ -1,4 +1,9 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { + ChannelMemberResponse, + ChannelResponse, + MessageResponse, + ReadResponse, +} from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; import { generateUser, getUserDefaults } from './user'; @@ -54,9 +59,21 @@ const defaultState = { setIsUpToDate: jest.fn(), }; +export type GeneratedChannel = { + _client: Record; + channel: Partial & { config: typeof defaultConfig }; + cid: string; + id: string; + messages: Partial[]; + state: typeof defaultState; + type: string; +}; + +type GeneratedChannelIdType = { id?: string; type?: string }; + const getChannelDefaults = ( - { id, type }: { [key: string]: any } = { id: uuidv4(), type: 'messaging' }, -) => ({ + { id, type }: GeneratedChannelIdType = { id: uuidv4(), type: 'messaging' }, +): GeneratedChannel => ({ _client: {}, channel: { cid: `${type}:${id}`, @@ -74,34 +91,45 @@ const getChannelDefaults = ( updated_at: '2020-04-28T11:20:48.578147Z', }, cid: `${type}:${id}`, - id, + id: id as string, messages: [], state: defaultState, - type, + type: type as string, }); -export const generateChannel = (customValues: { [key: string]: any }) => - Object.keys(customValues).reduce((accumulated, current) => { +export const generateChannel = ( + customValues: Partial & Record = {}, +): GeneratedChannel => + Object.keys(customValues).reduce((accumulated, current) => { + const key = current as keyof GeneratedChannel; if (current in accumulated) { - const key = current as keyof typeof accumulated; - accumulated[key] = + (accumulated as Record)[current] = typeof accumulated[key] === 'object' - ? { ...accumulated[key], ...customValues[key] } - : (accumulated[key] = customValues[key]); + ? { ...(accumulated[key] as object), ...(customValues[current] as object) } + : customValues[current]; return accumulated; } - return { ...accumulated, [current]: customValues[current] }; + return { ...accumulated, [current]: customValues[current] } as GeneratedChannel; }, getChannelDefaults()); +export type GeneratedChannelResponseCustomValues = { + channel?: Partial; + id?: string; + messages?: Partial[]; + members?: Partial[]; + read?: Partial[]; + type?: string; +}; + export const generateChannelResponse = ( - customValues: { - channel?: Record; - id?: string; - messages?: Record[]; - members?: Record[]; - read?: Record[]; - type?: string; - } = { channel: {}, id: uuidv4(), members: [], messages: [], read: [], type: 'messaging' }, + customValues: GeneratedChannelResponseCustomValues = { + channel: {}, + id: uuidv4(), + members: [], + messages: [], + read: [], + type: 'messaging', + }, ) => { const { channel = {}, diff --git a/package/src/mock-builders/generator/member.js b/package/src/mock-builders/generator/member.js deleted file mode 100644 index 6c1bc2f412..0000000000 --- a/package/src/mock-builders/generator/member.js +++ /dev/null @@ -1,13 +0,0 @@ -import { generateUser } from './user'; - -export const generateMember = (options = {}) => { - const user = (options && options.user) || generateUser(); - return { - invited: false, - is_moderator: false, - role: 'member', - user, - user_id: user.id, - ...options, - }; -}; diff --git a/package/src/mock-builders/generator/member.ts b/package/src/mock-builders/generator/member.ts new file mode 100644 index 0000000000..2da156e8ed --- /dev/null +++ b/package/src/mock-builders/generator/member.ts @@ -0,0 +1,18 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ChannelMemberResponse } from 'stream-chat'; + +import { generateUser } from './user'; + +export const generateMember = ( + options: Partial = {}, +): ChannelMemberResponse => { + const user = (options && options.user) || generateUser(); + return fromPartial({ + invited: false, + is_moderator: false, + role: 'member', + user, + user_id: user.id, + ...options, + }); +}; diff --git a/package/src/mock-builders/generator/message.js b/package/src/mock-builders/generator/message.ts similarity index 64% rename from package/src/mock-builders/generator/message.js rename to package/src/mock-builders/generator/message.ts index c0ce3cdb58..c5edd6058f 100644 --- a/package/src/mock-builders/generator/message.js +++ b/package/src/mock-builders/generator/message.ts @@ -1,12 +1,16 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { MessageResponse } from 'stream-chat'; import { v4 as uuidv4, v5 as uuidv5 } from 'uuid'; import { generateUser } from './user'; -export const generateMessage = (options = {}) => { +type GenerateMessageOptions = Partial & { timestamp?: Date }; + +export const generateMessage = (options: GenerateMessageOptions = {}): MessageResponse => { const timestamp = options.timestamp || new Date(new Date().getTime() - Math.floor(Math.random() * 100000)); - return { + return fromPartial({ attachments: [], created_at: timestamp, html: '

regular

', @@ -17,11 +21,15 @@ export const generateMessage = (options = {}) => { updated_at: timestamp.toString(), user: generateUser(), ...options, - }; + }); }; const StreamReactNativeNamespace = '9b244ee4-7d69-4d7b-ae23-cf89e9f7b035'; -export const generateStaticMessage = (seed, options, date) => +export const generateStaticMessage = ( + seed: string, + options?: GenerateMessageOptions, + date?: string, +): MessageResponse => generateMessage({ created_at: date || '2020-04-27T13:39:49.331742Z', id: uuidv5(seed, StreamReactNativeNamespace), diff --git a/package/src/mock-builders/generator/reaction.js b/package/src/mock-builders/generator/reaction.js deleted file mode 100644 index bac0f07783..0000000000 --- a/package/src/mock-builders/generator/reaction.js +++ /dev/null @@ -1,12 +0,0 @@ -import { generateUser } from './user'; - -export const generateReaction = (options = {}) => { - const user = options.user || generateUser(); - return { - created_at: new Date(), - type: 'love', - user, - user_id: user.id, - ...options, - }; -}; diff --git a/package/src/mock-builders/generator/reaction.ts b/package/src/mock-builders/generator/reaction.ts new file mode 100644 index 0000000000..7f6420e016 --- /dev/null +++ b/package/src/mock-builders/generator/reaction.ts @@ -0,0 +1,15 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { ReactionResponse } from 'stream-chat'; + +import { generateUser } from './user'; + +export const generateReaction = (options: Partial = {}): ReactionResponse => { + const user = options.user || generateUser(); + return fromPartial({ + created_at: new Date(), + type: 'love', + user, + user_id: user.id, + ...options, + }); +}; diff --git a/package/src/mock-builders/generator/user.js b/package/src/mock-builders/generator/user.ts similarity index 51% rename from package/src/mock-builders/generator/user.js rename to package/src/mock-builders/generator/user.ts index 4ccf290795..e0aec0c55f 100644 --- a/package/src/mock-builders/generator/user.js +++ b/package/src/mock-builders/generator/user.ts @@ -1,22 +1,26 @@ +import { fromPartial } from '@total-typescript/shoehorn'; +import type { UserResponse } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; -export const getUserDefaults = () => ({ - banned: false, - created_at: '2020-04-27T13:39:49.331742Z', - id: uuidv4(), - image: uuidv4(), - name: uuidv4(), - online: false, - role: 'user', - updated_at: '2020-04-27T13:39:49.332087Z', -}); +export const getUserDefaults = (): UserResponse => + fromPartial({ + banned: false, + created_at: '2020-04-27T13:39:49.331742Z', + id: uuidv4(), + image: uuidv4(), + name: uuidv4(), + online: false, + role: 'user', + updated_at: '2020-04-27T13:39:49.332087Z', + }); -export const generateUser = (options = {}) => ({ - ...getUserDefaults(), - ...options, -}); +export const generateUser = (options: Partial = {}): UserResponse => + fromPartial({ + ...getUserDefaults(), + ...options, + }); -const staticUsers = [ +const staticUsers: UserResponse[] = [ // By the order of... generateUser({ id: 'tommy', @@ -40,7 +44,7 @@ const staticUsers = [ }), ]; -export const generateStaticUser = (userNumber) => { +export const generateStaticUser = (userNumber: number): UserResponse => { if (userNumber - 1 > staticUsers.length) { throw new Error(`Tried getting a static user that doesn't exist. Index: ${userNumber} , number of users: ${staticUsers.length}`); diff --git a/package/src/mock-builders/mock.js b/package/src/mock-builders/mock.js deleted file mode 100644 index 0ed83d81b1..0000000000 --- a/package/src/mock-builders/mock.js +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ -/* eslint no-param-reassign: 0 */ - -import { StreamChat } from 'stream-chat'; - -const apiKey = 'API_KEY'; -const token = 'dummy_token'; - -export const setUser = (client, user) => - new Promise((resolve) => { - client.connectionId = 'dumm_connection_id'; - client.user = user; - client.user.mutes = []; - client._user = { ...user }; - client.userID = user.id; - client.userToken = token; - resolve(); - }); - -function mockClient(client, options = {}) { - const { disableAppSettings = true } = options; - - jest.spyOn(client, '_setToken').mockImplementation(); - jest.spyOn(client, '_setupConnection').mockImplementation(); - client.tokenManager = { - getToken: jest.fn(() => token), - tokenReady: jest.fn(() => true), - }; - client.setUser = setUser.bind(null, client); - - if (disableAppSettings) { - client.getAppSettings = jest.fn(() => ({})); - } - - return client; -} - -export const getTestClient = (options = {}) => mockClient(new StreamChat(apiKey), options); - -export const getTestClientWithUser = async (user, options = {}) => { - const { disableAppSettings = true } = options; - const client = mockClient(new StreamChat(apiKey)); - await setUser(client, user); - client.wsPromise = Promise.resolve(); - - if (disableAppSettings) { - client.getAppSettings = jest.fn(() => ({})); - } - - return client; -}; - -export const getRandomInt = (min, max) => { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min)) + min; // The maximum is exclusive and the minimum is inclusive -}; diff --git a/package/src/mock-builders/mock.ts b/package/src/mock-builders/mock.ts new file mode 100644 index 0000000000..c228e335f7 --- /dev/null +++ b/package/src/mock-builders/mock.ts @@ -0,0 +1,79 @@ +/* eslint no-underscore-dangle: 0 */ +/* eslint no-param-reassign: 0 */ + +import { StreamChat, type UserResponse } from 'stream-chat'; + +const apiKey = 'API_KEY'; +const token = 'dummy_token'; + +type MockClientOptions = { disableAppSettings?: boolean }; + +// Tests reach into private/internal StreamChat fields to set up a mocked +// authenticated client without going through the real network handshake. +type MockableStreamChat = StreamChat & { + connectionId?: string; + _user?: UserResponse; + userToken?: string; + setUser?: (user: UserResponse) => Promise; + wsPromise?: Promise; + _setToken?: (...args: unknown[]) => unknown; + _setupConnection?: (...args: unknown[]) => unknown; +}; + +export const setUser = (client: StreamChat, user: UserResponse): Promise => + new Promise((resolve) => { + const c = client as MockableStreamChat; + c.connectionId = 'dumm_connection_id'; + c.user = user; + if (c.user) (c.user as { mutes?: unknown[] }).mutes = []; + c._user = { ...user }; + c.userID = user.id; + c.userToken = token; + resolve(); + }); + +function mockClient(client: StreamChat, options: MockClientOptions = {}): StreamChat { + const { disableAppSettings = true } = options; + const c = client as MockableStreamChat; + + jest.spyOn(c as unknown as Record, '_setToken' as never).mockImplementation(); + jest + .spyOn(c as unknown as Record, '_setupConnection' as never) + .mockImplementation(); + c.tokenManager = { + getToken: jest.fn(() => token), + tokenReady: jest.fn(() => true), + } as unknown as StreamChat['tokenManager']; + c.setUser = setUser.bind(null, client); + + if (disableAppSettings) { + c.getAppSettings = jest.fn(() => ({})) as unknown as StreamChat['getAppSettings']; + } + + return client; +} + +export const getTestClient = (options: MockClientOptions = {}): StreamChat => + mockClient(new StreamChat(apiKey), options); + +export const getTestClientWithUser = async ( + user: UserResponse, + options: MockClientOptions = {}, +): Promise => { + const { disableAppSettings = true } = options; + const client = mockClient(new StreamChat(apiKey)); + await setUser(client, user); + (client as MockableStreamChat).wsPromise = Promise.resolve(); + + if (disableAppSettings) { + client.getAppSettings = jest.fn(() => ({})) as unknown as StreamChat['getAppSettings']; + } + + return client; +}; + +export const getRandomInt = (min: number, max: number): number => { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min)) + min; // The maximum is exclusive and the minimum is inclusive +}; diff --git a/package/src/test-utils/BetterSqlite.js b/package/src/test-utils/BetterSqlite.js deleted file mode 100644 index 340c8cf485..0000000000 --- a/package/src/test-utils/BetterSqlite.js +++ /dev/null @@ -1,36 +0,0 @@ -import Database from 'better-sqlite3'; - -import { tables } from '../store/schema'; - -export class BetterSqlite { - db = null; - - static openDB = () => { - this.db = new Database('foobar.db'); - }; - - static closeDB = () => { - this.db.close(); - }; - - static getTables = async () => { - const tablesInDb = await this.db.pragma('table_list;'); - return tablesInDb; - }; - - static dropAllTables = () => { - const tableNames = Object.keys(tables); - - tableNames.forEach((name) => { - const stmt = this.db.prepare(`DROP TABLE IF EXISTS ${name}`); - stmt.run(); - }); - }; - - static selectFromTable = async (table) => { - const stmt = await this.db.prepare(`SELECT * FROM ${table}`); - const result = stmt.all(); - - return result; - }; -} diff --git a/package/src/test-utils/BetterSqlite.ts b/package/src/test-utils/BetterSqlite.ts new file mode 100644 index 0000000000..73666cc585 --- /dev/null +++ b/package/src/test-utils/BetterSqlite.ts @@ -0,0 +1,38 @@ +import Database, { type Database as DatabaseType } from 'better-sqlite3'; + +import { tables } from '../store/schema'; + +export class BetterSqlite { + static db: DatabaseType | null = null; + + static openDB = (): void => { + BetterSqlite.db = new Database('foobar.db'); + }; + + static closeDB = (): void => { + BetterSqlite.db?.close(); + }; + + static getTables = async (): Promise => { + const tablesInDb = await BetterSqlite.db?.pragma('table_list;'); + return tablesInDb; + }; + + static dropAllTables = (): void => { + const tableNames = Object.keys(tables); + + tableNames.forEach((name) => { + const stmt = BetterSqlite.db?.prepare(`DROP TABLE IF EXISTS ${name}`); + stmt?.run(); + }); + }; + + static selectFromTable = async >( + table: string, + ): Promise => { + const stmt = await BetterSqlite.db?.prepare(`SELECT * FROM ${table}`); + const result = (stmt?.all() ?? []) as TRow[]; + + return result; + }; +} diff --git a/package/src/utils/__tests__/Streami18n.test.js b/package/src/utils/__tests__/Streami18n.test.ts similarity index 100% rename from package/src/utils/__tests__/Streami18n.test.js rename to package/src/utils/__tests__/Streami18n.test.ts diff --git a/package/src/utils/__tests__/utils.test.js b/package/src/utils/__tests__/utils.test.ts similarity index 100% rename from package/src/utils/__tests__/utils.test.js rename to package/src/utils/__tests__/utils.test.ts diff --git a/package/tsconfig.test.json b/package/tsconfig.test.json new file mode 100644 index 0000000000..8dae735147 --- /dev/null +++ b/package/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitAny": false + }, + "include": ["./src/**/*"], + "exclude": [ + "./src/components/docs/*", + "./src/emoji-data/*.js", + "./src/styleguideComponents", + "node_modules" + ] +} diff --git a/package/yarn.lock b/package/yarn.lock index 6a6ea61ebc..d47d87ee9e 100644 --- a/package/yarn.lock +++ b/package/yarn.lock @@ -1595,50 +1595,49 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.0.0.tgz#7f8f66adc20ea795cc74afb74280e08947e55c13" - integrity sha512-vfpJap6JZQ3I8sUN8dsFqNAKJYO4KIGxkcB+3Fw7Q/BJiWY5HwtMMiuT1oP0avsiDhjE/TCLaDgbGfHwDdBVeg== +"@jest/console@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.3.0.tgz#42ccc3f995d400a8fe35b8850cfe10a8d4804cdf" + integrity sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww== dependencies: - "@jest/types" "30.0.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" - jest-message-util "30.0.0" - jest-util "30.0.0" + jest-message-util "30.3.0" + jest-util "30.3.0" slash "^3.0.0" -"@jest/core@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.0.0.tgz#2ea3e63dd193af0b986f70b01c2597efd0e10b27" - integrity sha512-1zU39zFtWSl5ZuDK3Rd6P8S28MmS4F11x6Z4CURrgJ99iaAJg68hmdJ2SAHEEO6ociaNk43UhUYtHxWKEWoNYw== - dependencies: - "@jest/console" "30.0.0" - "@jest/pattern" "30.0.0" - "@jest/reporters" "30.0.0" - "@jest/test-result" "30.0.0" - "@jest/transform" "30.0.0" - "@jest/types" "30.0.0" +"@jest/core@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.3.0.tgz#d06bb8456f35350f6494fd2405bcec4abb97b994" + integrity sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw== + dependencies: + "@jest/console" "30.3.0" + "@jest/pattern" "30.0.1" + "@jest/reporters" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" ansi-escapes "^4.3.2" chalk "^4.1.2" ci-info "^4.2.0" exit-x "^0.2.2" graceful-fs "^4.2.11" - jest-changed-files "30.0.0" - jest-config "30.0.0" - jest-haste-map "30.0.0" - jest-message-util "30.0.0" - jest-regex-util "30.0.0" - jest-resolve "30.0.0" - jest-resolve-dependencies "30.0.0" - jest-runner "30.0.0" - jest-runtime "30.0.0" - jest-snapshot "30.0.0" - jest-util "30.0.0" - jest-validate "30.0.0" - jest-watcher "30.0.0" - micromatch "^4.0.8" - pretty-format "30.0.0" + jest-changed-files "30.3.0" + jest-config "30.3.0" + jest-haste-map "30.3.0" + jest-message-util "30.3.0" + jest-regex-util "30.0.1" + jest-resolve "30.3.0" + jest-resolve-dependencies "30.3.0" + jest-runner "30.3.0" + jest-runtime "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" + jest-watcher "30.3.0" + pretty-format "30.3.0" slash "^3.0.0" "@jest/create-cache-key-function@^29.7.0": @@ -1648,20 +1647,20 @@ dependencies: "@jest/types" "^29.6.3" -"@jest/diff-sequences@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.0.tgz#402d27d14e9d5161dedfca98bf181018a8931eb1" - integrity sha512-xMbtoCeKJDto86GW6AiwVv7M4QAuI56R7dVBr1RNGYbOT44M2TIzOiske2RxopBqkumDY+A1H55pGvuribRY9A== +"@jest/diff-sequences@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz#25b0818d3d83f00b9c7b04e069b8810f9014b143" + integrity sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA== -"@jest/environment@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.0.0.tgz#d66484e35d6ee9a551d2ef3adb9e18728f0e4736" - integrity sha512-09sFbMMgS5JxYnvgmmtwIHhvoyzvR5fUPrVl8nOCrC5KdzmmErTcAxfWyAhJ2bv3rvHNQaKiS+COSG+O7oNbXw== +"@jest/environment@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.3.0.tgz#b0657c2944b6ef3352f7b25903cc3a23e6ab70f6" + integrity sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw== dependencies: - "@jest/fake-timers" "30.0.0" - "@jest/types" "30.0.0" + "@jest/fake-timers" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" - jest-mock "30.0.0" + jest-mock "30.3.0" "@jest/environment@^29.7.0": version "29.7.0" @@ -1673,39 +1672,32 @@ "@types/node" "*" jest-mock "^29.7.0" -"@jest/expect-utils@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.0.tgz#118d41d9df420db61d307308848a9e12f0fc1fad" - integrity sha512-UiWfsqNi/+d7xepfOv8KDcbbzcYtkWBe3a3kVDtg6M1kuN6CJ7b4HzIp5e1YHrSaQaVS8sdCoyCMCZClTLNKFQ== - dependencies: - "@jest/get-type" "30.0.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== +"@jest/expect-utils@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.3.0.tgz#c45b2da9802ffed33bf43b3e019ddb95e5ad95e8" + integrity sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA== dependencies: - jest-get-type "^29.6.3" + "@jest/get-type" "30.1.0" -"@jest/expect@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.0.0.tgz#3f6c17a333444aa6d93b507871815c24c6681f21" - integrity sha512-XZ3j6syhMeKiBknmmc8V3mNIb44kxLTbOQtaXA4IFdHy+vEN0cnXRzbRjdGBtrp4k1PWyMWNU3Fjz3iejrhpQg== +"@jest/expect@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.3.0.tgz#08ee7f5b610167b0068743246c0b568f4c40c773" + integrity sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg== dependencies: - expect "30.0.0" - jest-snapshot "30.0.0" + expect "30.3.0" + jest-snapshot "30.3.0" -"@jest/fake-timers@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.0.0.tgz#4d4ae90695609c1b27795ad1210203d73f30dcfd" - integrity sha512-yzBmJcrMHAMcAEbV2w1kbxmx8WFpEz8Cth3wjLMSkq+LO8VeGKRhpr5+BUp7PPK+x4njq/b6mVnDR8e/tPL5ng== +"@jest/fake-timers@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.3.0.tgz#2b2868130c1d28233a79566874c42cae1c5a70bc" + integrity sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ== dependencies: - "@jest/types" "30.0.0" - "@sinonjs/fake-timers" "^13.0.0" + "@jest/types" "30.3.0" + "@sinonjs/fake-timers" "^15.0.0" "@types/node" "*" - jest-message-util "30.0.0" - jest-mock "30.0.0" - jest-util "30.0.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-util "30.3.0" "@jest/fake-timers@^29.7.0": version "29.7.0" @@ -1719,20 +1711,20 @@ jest-mock "^29.7.0" jest-util "^29.7.0" -"@jest/get-type@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.0.tgz#59dcb5a9cbd9eb0004d3a2ed2fa9c9c3abfbf005" - integrity sha512-VZWMjrBzqfDKngQ7sUctKeLxanAbsBFoZnPxNIG6CmxK7Gv6K44yqd0nzveNIBfuhGZMmk1n5PGbvdSTOu0yTg== +"@jest/get-type@30.1.0": + version "30.1.0" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" + integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== -"@jest/globals@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.0.0.tgz#b80a488ec3fc99637455def038e53cfcd562a18f" - integrity sha512-OEzYes5A1xwBJVMPqFRa8NCao8Vr42nsUZuf/SpaJWoLE+4kyl6nCQZ1zqfipmCrIXQVALC5qJwKy/7NQQLPhw== +"@jest/globals@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.3.0.tgz#40f4c90e5602629ecda1ca773a8fb21575bb64ea" + integrity sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA== dependencies: - "@jest/environment" "30.0.0" - "@jest/expect" "30.0.0" - "@jest/types" "30.0.0" - jest-mock "30.0.0" + "@jest/environment" "30.3.0" + "@jest/expect" "30.3.0" + "@jest/types" "30.3.0" + jest-mock "30.3.0" "@jest/pattern@30.0.0": version "30.0.0" @@ -1742,31 +1734,39 @@ "@types/node" "*" jest-regex-util "30.0.0" -"@jest/reporters@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.0.0.tgz#a384cc5692e3288617f6993c3267314f8f865781" - integrity sha512-5WHNlLO0Ok+/o6ML5IzgVm1qyERtLHBNhwn67PAq92H4hZ+n5uW/BYj1VVwmTdxIcNrZLxdV9qtpdZkXf16HxA== +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + +"@jest/reporters@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.3.0.tgz#0c1065f6c892665e5a051df22b19df4466ed816b" + integrity sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "30.0.0" - "@jest/test-result" "30.0.0" - "@jest/transform" "30.0.0" - "@jest/types" "30.0.0" + "@jest/console" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@jridgewell/trace-mapping" "^0.3.25" "@types/node" "*" chalk "^4.1.2" collect-v8-coverage "^1.0.2" exit-x "^0.2.2" - glob "^10.3.10" + glob "^10.5.0" graceful-fs "^4.2.11" istanbul-lib-coverage "^3.0.0" istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^5.0.0" istanbul-reports "^3.1.3" - jest-message-util "30.0.0" - jest-util "30.0.0" - jest-worker "30.0.0" + jest-message-util "30.3.0" + jest-util "30.3.0" + jest-worker "30.3.0" slash "^3.0.0" string-length "^4.0.2" v8-to-istanbul "^9.0.1" @@ -1778,6 +1778,13 @@ dependencies: "@sinclair/typebox" "^0.34.0" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -1785,43 +1792,43 @@ dependencies: "@sinclair/typebox" "^0.27.8" -"@jest/snapshot-utils@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.0.0.tgz#95c34aa1e59840c53b91695132022bfeeeee650e" - integrity sha512-C/QSFUmvZEYptg2Vin84FggAphwHvj6la39vkw1CNOZQORWZ7O/H0BXmdeeeGnvlXDYY8TlFM5jgFnxLAxpFjA== +"@jest/snapshot-utils@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz#ca003c91a3e1e4e4956dee716a2aaf04b6707f31" + integrity sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g== dependencies: - "@jest/types" "30.0.0" + "@jest/types" "30.3.0" chalk "^4.1.2" graceful-fs "^4.2.11" natural-compare "^1.4.0" -"@jest/source-map@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-30.0.0.tgz#f1318656f6ca2cab188c5860d8d7ccb2f9a0396c" - integrity sha512-oYBJ4d/NF4ZY3/7iq1VaeoERHRvlwKtrGClgescaXMIa1mmb+vfJd0xMgbW9yrI80IUA7qGbxpBWxlITrHkWoA== +"@jest/source-map@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-30.0.1.tgz#305ebec50468f13e658b3d5c26f85107a5620aaa" + integrity sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg== dependencies: "@jridgewell/trace-mapping" "^0.3.25" callsites "^3.1.0" graceful-fs "^4.2.11" -"@jest/test-result@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.0.0.tgz#9a06e3b0f2024ace56a2989075c2c8938aae5297" - integrity sha512-685zco9HdgBaaWiB9T4xjLtBuN0Q795wgaQPpmuAeZPHwHZSoKFAUnozUtU+ongfi4l5VCz8AclOE5LAQdyjxQ== +"@jest/test-result@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.3.0.tgz#cd8882d683d467fcffb98c09501a65687a76aae9" + integrity sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ== dependencies: - "@jest/console" "30.0.0" - "@jest/types" "30.0.0" + "@jest/console" "30.3.0" + "@jest/types" "30.3.0" "@types/istanbul-lib-coverage" "^2.0.6" collect-v8-coverage "^1.0.2" -"@jest/test-sequencer@30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.0.0.tgz#7052c0c6d56580f9096b6c3d02834220df676340" - integrity sha512-Hmvv5Yg6UmghXIcVZIydkT0nAK7M/hlXx9WMHR5cLVwdmc14/qUQt3mC72T6GN0olPC6DhmKE6Cd/pHsgDbuqQ== +"@jest/test-sequencer@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz#27002b2093f4e0d9e0e1ebb0bc274a242fdadc14" + integrity sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA== dependencies: - "@jest/test-result" "30.0.0" + "@jest/test-result" "30.3.0" graceful-fs "^4.2.11" - jest-haste-map "30.0.0" + jest-haste-map "30.3.0" slash "^3.0.0" "@jest/transform@30.0.0": @@ -1845,6 +1852,26 @@ slash "^3.0.0" write-file-atomic "^5.0.1" +"@jest/transform@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.3.0.tgz#9e6f78ffa205449bf956e269fd707c160f47ce2f" + integrity sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A== + dependencies: + "@babel/core" "^7.27.4" + "@jest/types" "30.3.0" + "@jridgewell/trace-mapping" "^0.3.25" + babel-plugin-istanbul "^7.0.1" + chalk "^4.1.2" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-haste-map "30.3.0" + jest-regex-util "30.0.1" + jest-util "30.3.0" + pirates "^4.0.7" + slash "^3.0.0" + write-file-atomic "^5.0.1" + "@jest/transform@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" @@ -1879,6 +1906,19 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" +"@jest/types@30.3.0": + version "30.3.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.3.0.tgz#cada800d323cb74945c24ac74615fdb312a6c85f" + integrity sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" @@ -2201,10 +2241,10 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@sinonjs/fake-timers@^13.0.0": - version "13.0.5" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" - integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== +"@sinonjs/fake-timers@^15.0.0": + version "15.3.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz#afecc36681e26aab9e0fe809fd9ad578096a3058" + integrity sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw== dependencies: "@sinonjs/commons" "^3.0.1" @@ -2310,6 +2350,11 @@ pretty-format "^29.7.0" redent "^3.0.0" +"@total-typescript/shoehorn@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@total-typescript/shoehorn/-/shoehorn-0.1.2.tgz#a0c095ce8cb9b4ae3556bcff42702ddb072e9d18" + integrity sha512-p7nNZbOZIofpDNyP0u1BctFbjxD44Qc+oO5jufgQdFdGIXJLc33QRloJpq7k5T59CTgLWfQSUxsuqLcmeurYRw== + "@tybys/wasm-util@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355" @@ -2401,13 +2446,13 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.14": - version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" + expect "^30.0.0" + pretty-format "^30.0.0" "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": version "7.0.15" @@ -3123,6 +3168,19 @@ babel-jest@30.0.0: graceful-fs "^4.2.11" slash "^3.0.0" +babel-jest@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.3.0.tgz#3ff5553fa3bcbb8738d2d7335a4dbdc3bd1a0eb5" + integrity sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ== + dependencies: + "@jest/transform" "30.3.0" + "@types/babel__core" "^7.20.5" + babel-plugin-istanbul "^7.0.1" + babel-preset-jest "30.3.0" + chalk "^4.1.2" + graceful-fs "^4.2.11" + slash "^3.0.0" + babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -3165,6 +3223,17 @@ babel-plugin-istanbul@^7.0.0: istanbul-lib-instrument "^6.0.2" test-exclude "^6.0.0" +babel-plugin-istanbul@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz#d8b518c8ea199364cf84ccc82de89740236daf92" + integrity sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-instrument "^6.0.2" + test-exclude "^6.0.0" + babel-plugin-jest-hoist@30.0.0: version "30.0.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.0.tgz#76c9bf58316ebb7026d671d71d26138ae415326b" @@ -3174,6 +3243,13 @@ babel-plugin-jest-hoist@30.0.0: "@babel/types" "^7.27.3" "@types/babel__core" "^7.20.5" +babel-plugin-jest-hoist@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz#235ad714a45c18b12566becf439e1c604e277015" + integrity sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg== + dependencies: + "@types/babel__core" "^7.20.5" + babel-plugin-jest-hoist@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" @@ -3269,6 +3345,27 @@ babel-preset-current-node-syntax@^1.0.0, babel-preset-current-node-syntax@^1.1.0 "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" +babel-preset-current-node-syntax@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + babel-preset-jest@30.0.0: version "30.0.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.0.0.tgz#54b16c96c1b687b9c72baa37a00b01fe9be4c4f3" @@ -3277,6 +3374,14 @@ babel-preset-jest@30.0.0: babel-plugin-jest-hoist "30.0.0" babel-preset-current-node-syntax "^1.1.0" +babel-preset-jest@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz#21cf3d19a6f5e9924426c879ee0b7f092636d043" + integrity sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ== + dependencies: + babel-plugin-jest-hoist "30.3.0" + babel-preset-current-node-syntax "^1.2.0" + babel-preset-jest@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" @@ -4518,28 +4623,17 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -expect@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.0.tgz#460dfda282e0a8de8302aabee951dba7e79a5a53" - integrity sha512-xCdPp6gwiR9q9lsPCHANarIkFTN/IMZso6Kkq03sOm9IIGtzK/UJqml0dkhHibGh8HKOj8BIDIpZ0BZuU7QK6w== - dependencies: - "@jest/expect-utils" "30.0.0" - "@jest/get-type" "30.0.0" - jest-matcher-utils "30.0.0" - jest-message-util "30.0.0" - jest-mock "30.0.0" - jest-util "30.0.0" - -expect@^29.0.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== +expect@30.3.0, expect@^30.0.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.3.0.tgz#1b82111517d1ab030f3db0cf1b4061c8aa644f61" + integrity sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q== dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" + "@jest/expect-utils" "30.3.0" + "@jest/get-type" "30.1.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-util "30.3.0" exponential-backoff@^3.1.1: version "3.1.2" @@ -4861,10 +4955,10 @@ glob@13.0.0: minipass "^7.1.2" path-scurry "^2.0.0" -glob@^10.3.10: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== +glob@^10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -5668,96 +5762,95 @@ jackspeak@^4.0.1: dependencies: "@isaacs/cliui" "^8.0.2" -jest-changed-files@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.0.0.tgz#2993fc97acdf701b286310bf672a88a797b57e64" - integrity sha512-rzGpvCdPdEV1Ma83c1GbZif0L2KAm3vXSXGRlpx7yCt0vhruwCNouKNRh3SiVcISHP1mb3iJzjb7tAEnNu1laQ== +jest-changed-files@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.3.0.tgz#055849df695f9a9fcde0ae44024f815bbc627f3a" + integrity sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA== dependencies: execa "^5.1.1" - jest-util "30.0.0" + jest-util "30.3.0" p-limit "^3.1.0" -jest-circus@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.0.0.tgz#f5d32ef11dcef9beba7ee78f32dd2c82b5f51097" - integrity sha512-nTwah78qcKVyndBS650hAkaEmwWGaVsMMoWdJwMnH77XArRJow2Ir7hc+8p/mATtxVZuM9OTkA/3hQocRIK5Dw== +jest-circus@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.3.0.tgz#153614c11ab35867f371bd93496ecb9690b92077" + integrity sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA== dependencies: - "@jest/environment" "30.0.0" - "@jest/expect" "30.0.0" - "@jest/test-result" "30.0.0" - "@jest/types" "30.0.0" + "@jest/environment" "30.3.0" + "@jest/expect" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" co "^4.6.0" dedent "^1.6.0" is-generator-fn "^2.1.0" - jest-each "30.0.0" - jest-matcher-utils "30.0.0" - jest-message-util "30.0.0" - jest-runtime "30.0.0" - jest-snapshot "30.0.0" - jest-util "30.0.0" + jest-each "30.3.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-runtime "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" p-limit "^3.1.0" - pretty-format "30.0.0" + pretty-format "30.3.0" pure-rand "^7.0.0" slash "^3.0.0" stack-utils "^2.0.6" -jest-cli@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.0.0.tgz#d689f093e6019bd86e76407b431fae2f8beb85fe" - integrity sha512-fWKAgrhlwVVCfeizsmIrPRTBYTzO82WSba3gJniZNR3PKXADgdC0mmCSK+M+t7N8RCXOVfY6kvCkvjUNtzmHYQ== +jest-cli@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.3.0.tgz#5ed75a337f486a1f1c5acbb2de8acddb106ead6c" + integrity sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw== dependencies: - "@jest/core" "30.0.0" - "@jest/test-result" "30.0.0" - "@jest/types" "30.0.0" + "@jest/core" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" chalk "^4.1.2" exit-x "^0.2.2" import-local "^3.2.0" - jest-config "30.0.0" - jest-util "30.0.0" - jest-validate "30.0.0" + jest-config "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" yargs "^17.7.2" -jest-config@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.0.0.tgz#77387de024f5a1b456be844f80a1390e8ef19699" - integrity sha512-p13a/zun+sbOMrBnTEUdq/5N7bZMOGd1yMfqtAJniPNuzURMay4I+vxZLK1XSDbjvIhmeVdG8h8RznqYyjctyg== +jest-config@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.3.0.tgz#b969e0aaaf5964419e62953bb712c16d15972425" + integrity sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w== dependencies: "@babel/core" "^7.27.4" - "@jest/get-type" "30.0.0" - "@jest/pattern" "30.0.0" - "@jest/test-sequencer" "30.0.0" - "@jest/types" "30.0.0" - babel-jest "30.0.0" + "@jest/get-type" "30.1.0" + "@jest/pattern" "30.0.1" + "@jest/test-sequencer" "30.3.0" + "@jest/types" "30.3.0" + babel-jest "30.3.0" chalk "^4.1.2" ci-info "^4.2.0" deepmerge "^4.3.1" - glob "^10.3.10" + glob "^10.5.0" graceful-fs "^4.2.11" - jest-circus "30.0.0" - jest-docblock "30.0.0" - jest-environment-node "30.0.0" - jest-regex-util "30.0.0" - jest-resolve "30.0.0" - jest-runner "30.0.0" - jest-util "30.0.0" - jest-validate "30.0.0" - micromatch "^4.0.8" + jest-circus "30.3.0" + jest-docblock "30.2.0" + jest-environment-node "30.3.0" + jest-regex-util "30.0.1" + jest-resolve "30.3.0" + jest-runner "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" parse-json "^5.2.0" - pretty-format "30.0.0" + pretty-format "30.3.0" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.0.tgz#d3d4f75e257e3c2cb8729438fe9cec66098f6176" - integrity sha512-TgT1+KipV8JTLXXeFX0qSvIJR/UXiNNojjxb/awh3vYlBZyChU/NEmyKmq+wijKjWEztyrGJFL790nqMqNjTHA== +jest-diff@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.3.0.tgz#e0a4c84ef350ffd790ffd5b0016acabeecf5f759" + integrity sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ== dependencies: - "@jest/diff-sequences" "30.0.0" - "@jest/get-type" "30.0.0" + "@jest/diff-sequences" "30.3.0" + "@jest/get-type" "30.1.0" chalk "^4.1.2" - pretty-format "30.0.0" + pretty-format "30.3.0" jest-diff@^29.0.1, jest-diff@^29.7.0: version "29.7.0" @@ -5769,36 +5862,36 @@ jest-diff@^29.0.1, jest-diff@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-docblock@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.0.0.tgz#1650e0ded4fa92ff1adeda2050641705b6b300db" - integrity sha512-By/iQ0nvTzghEecGzUMCp1axLtBh+8wB4Hpoi5o+x1stycjEmPcH1mHugL4D9Q+YKV++vKeX/3ZTW90QC8ICPg== +jest-docblock@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.2.0.tgz#42cd98d69f887e531c7352309542b1ce4ee10256" + integrity sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA== dependencies: detect-newline "^3.1.0" -jest-each@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.0.0.tgz#f3760fba22074c4e82b440f4a0557467f464f718" - integrity sha512-qkFEW3cfytEjG2KtrhwtldZfXYnWSanO8xUMXLe4A6yaiHMHJUalk0Yyv4MQH6aeaxgi4sGVrukvF0lPMM7U1w== +jest-each@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.3.0.tgz#faa7229bf7a9fa6426dc604057a7d2a173493b1e" + integrity sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA== dependencies: - "@jest/get-type" "30.0.0" - "@jest/types" "30.0.0" + "@jest/get-type" "30.1.0" + "@jest/types" "30.3.0" chalk "^4.1.2" - jest-util "30.0.0" - pretty-format "30.0.0" + jest-util "30.3.0" + pretty-format "30.3.0" -jest-environment-node@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.0.0.tgz#0d16b29f5720c796d8eadd9c22ada1c1c43d3ba2" - integrity sha512-sF6lxyA25dIURyDk4voYmGU9Uwz2rQKMfjxKnDd19yk+qxKGrimFqS5YsPHWTlAVBo+YhWzXsqZoaMzrTFvqfg== +jest-environment-node@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.3.0.tgz#aa8a57c5d0c4af0f8b1f7403ba737fec6b3aabbe" + integrity sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ== dependencies: - "@jest/environment" "30.0.0" - "@jest/fake-timers" "30.0.0" - "@jest/types" "30.0.0" + "@jest/environment" "30.3.0" + "@jest/fake-timers" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" - jest-mock "30.0.0" - jest-util "30.0.0" - jest-validate "30.0.0" + jest-mock "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" jest-environment-node@^29.7.0: version "29.7.0" @@ -5835,6 +5928,24 @@ jest-haste-map@30.0.0: optionalDependencies: fsevents "^2.3.3" +jest-haste-map@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.3.0.tgz#1ea6843e6e45c077d91270666a4fcba958c24cd5" + integrity sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA== + dependencies: + "@jest/types" "30.3.0" + "@types/node" "*" + anymatch "^3.1.3" + fb-watchman "^2.0.2" + graceful-fs "^4.2.11" + jest-regex-util "30.0.1" + jest-util "30.3.0" + jest-worker "30.3.0" + picomatch "^4.0.3" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.3" + jest-haste-map@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" @@ -5854,23 +5965,23 @@ jest-haste-map@^29.7.0: optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.0.0.tgz#056d168e6f308262b40ad05843723a52cdb58b91" - integrity sha512-E/ly1azdVVbZrS0T6FIpyYHvsdek4FNaThJTtggjV/8IpKxh3p9NLndeUZy2+sjAI3ncS+aM0uLLon/dBg8htA== +jest-leak-detector@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz#a695a851e353f517a554a2f5c91c2742fc131c98" + integrity sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ== dependencies: - "@jest/get-type" "30.0.0" - pretty-format "30.0.0" + "@jest/get-type" "30.1.0" + pretty-format "30.3.0" -jest-matcher-utils@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.0.tgz#f72a65e248c0462795f7e14386682bfee6ad4386" - integrity sha512-m5mrunqopkrqwG1mMdJxe1J4uGmS9AHHKYUmoxeQOxBcLjEvirIrIDwuKmUYrecPHVB/PUBpXs2gPoeA2FSSLQ== +jest-matcher-utils@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz#d6c739fec1ecd33809f2d2b1348f6ab01d2f2493" + integrity sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA== dependencies: - "@jest/get-type" "30.0.0" + "@jest/get-type" "30.1.0" chalk "^4.1.2" - jest-diff "30.0.0" - pretty-format "30.0.0" + jest-diff "30.3.0" + pretty-format "30.3.0" jest-matcher-utils@^29.0.1, jest-matcher-utils@^29.7.0: version "29.7.0" @@ -5882,18 +5993,18 @@ jest-matcher-utils@^29.0.1, jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-message-util@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.0.tgz#b115d408cd877a6e3e711485a3bd240c7a27503c" - integrity sha512-pV3qcrb4utEsa/U7UI2VayNzSDQcmCllBZLSoIucrESRu0geKThFZOjjh0kACDJFJRAQwsK7GVsmS6SpEceD8w== +jest-message-util@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.3.0.tgz#4d723544d36890ba862ac3961db52db5b0d1ba39" + integrity sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw== dependencies: "@babel/code-frame" "^7.27.1" - "@jest/types" "30.0.0" + "@jest/types" "30.3.0" "@types/stack-utils" "^2.0.3" chalk "^4.1.2" graceful-fs "^4.2.11" - micromatch "^4.0.8" - pretty-format "30.0.0" + picomatch "^4.0.3" + pretty-format "30.3.0" slash "^3.0.0" stack-utils "^2.0.6" @@ -5912,14 +6023,14 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.0.tgz#f3b3115cd80c3eec7df93809430ab1feaeeb7229" - integrity sha512-W2sRA4ALXILrEetEOh2ooZG6fZ01iwVs0OWMKSSWRcUlaLr4ESHuiKXDNTg+ZVgOq8Ei5445i/Yxrv59VT+XkA== +jest-mock@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.3.0.tgz#e0fa4184a596a6c4fdec53d4f412158418923747" + integrity sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog== dependencies: - "@jest/types" "30.0.0" + "@jest/types" "30.3.0" "@types/node" "*" - jest-util "30.0.0" + jest-util "30.3.0" jest-mock@^29.7.0: version "29.7.0" @@ -5940,113 +6051,118 @@ jest-regex-util@30.0.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.0.tgz#031f385ebb947e770e409ede703d200b3405413e" integrity sha512-rT84010qRu/5OOU7a9TeidC2Tp3Qgt9Sty4pOZ/VSDuEmRupIjKZAb53gU3jr4ooMlhwScrgC9UixJxWzVu9oQ== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== -jest-resolve-dependencies@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.0.tgz#caf6829daa9ad6579a6da7c2723346761102ef83" - integrity sha512-Yhh7odCAUNXhluK1bCpwIlHrN1wycYaTlZwq1GdfNBEESNNI/z1j1a7dUEWHbmB9LGgv0sanxw3JPmWU8NeebQ== +jest-resolve-dependencies@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz#4d638c9f0d93a62a6ed25dec874bfd7e756c8ce5" + integrity sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw== dependencies: - jest-regex-util "30.0.0" - jest-snapshot "30.0.0" + jest-regex-util "30.0.1" + jest-snapshot "30.3.0" -jest-resolve@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.0.0.tgz#8aaf8f85c8a14579fa34e651af406e57d2675092" - integrity sha512-zwWl1P15CcAfuQCEuxszjiKdsValhnWcj/aXg/R3aMHs8HVoCWHC4B/+5+1BirMoOud8NnN85GSP2LEZCbj3OA== +jest-resolve@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.3.0.tgz#b7bee9927279805b1b50715d2170a545553b87ff" + integrity sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g== dependencies: chalk "^4.1.2" graceful-fs "^4.2.11" - jest-haste-map "30.0.0" + jest-haste-map "30.3.0" jest-pnp-resolver "^1.2.3" - jest-util "30.0.0" - jest-validate "30.0.0" + jest-util "30.3.0" + jest-validate "30.3.0" slash "^3.0.0" unrs-resolver "^1.7.11" -jest-runner@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.0.0.tgz#d4667945181e3aecb025802a3f81ff30a523f877" - integrity sha512-xbhmvWIc8X1IQ8G7xTv0AQJXKjBVyxoVJEJgy7A4RXsSaO+k/1ZSBbHwjnUhvYqMvwQPomWssDkUx6EoidEhlw== +jest-runner@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.3.0.tgz#fa970fc4e45d418ad7e7d581b24cac7af5944cb7" + integrity sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw== dependencies: - "@jest/console" "30.0.0" - "@jest/environment" "30.0.0" - "@jest/test-result" "30.0.0" - "@jest/transform" "30.0.0" - "@jest/types" "30.0.0" + "@jest/console" "30.3.0" + "@jest/environment" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" emittery "^0.13.1" exit-x "^0.2.2" graceful-fs "^4.2.11" - jest-docblock "30.0.0" - jest-environment-node "30.0.0" - jest-haste-map "30.0.0" - jest-leak-detector "30.0.0" - jest-message-util "30.0.0" - jest-resolve "30.0.0" - jest-runtime "30.0.0" - jest-util "30.0.0" - jest-watcher "30.0.0" - jest-worker "30.0.0" + jest-docblock "30.2.0" + jest-environment-node "30.3.0" + jest-haste-map "30.3.0" + jest-leak-detector "30.3.0" + jest-message-util "30.3.0" + jest-resolve "30.3.0" + jest-runtime "30.3.0" + jest-util "30.3.0" + jest-watcher "30.3.0" + jest-worker "30.3.0" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.0.0.tgz#7aad9359da4054d4ae1ec8d94f83d3c07d6ce1c7" - integrity sha512-/O07qVgFrFAOGKGigojmdR3jUGz/y3+a/v9S/Yi2MHxsD+v6WcPppglZJw0gNJkRBArRDK8CFAwpM/VuEiiRjA== - dependencies: - "@jest/environment" "30.0.0" - "@jest/fake-timers" "30.0.0" - "@jest/globals" "30.0.0" - "@jest/source-map" "30.0.0" - "@jest/test-result" "30.0.0" - "@jest/transform" "30.0.0" - "@jest/types" "30.0.0" +jest-runtime@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.3.0.tgz#1a9bec7a9b68db12dfe4136bbe41ab883ea2c996" + integrity sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng== + dependencies: + "@jest/environment" "30.3.0" + "@jest/fake-timers" "30.3.0" + "@jest/globals" "30.3.0" + "@jest/source-map" "30.0.1" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" chalk "^4.1.2" cjs-module-lexer "^2.1.0" collect-v8-coverage "^1.0.2" - glob "^10.3.10" + glob "^10.5.0" graceful-fs "^4.2.11" - jest-haste-map "30.0.0" - jest-message-util "30.0.0" - jest-mock "30.0.0" - jest-regex-util "30.0.0" - jest-resolve "30.0.0" - jest-snapshot "30.0.0" - jest-util "30.0.0" + jest-haste-map "30.3.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-regex-util "30.0.1" + jest-resolve "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.0.0.tgz#44217201c3f935e7cc5b413c8dda05341c80b0d7" - integrity sha512-6oCnzjpvfj/UIOMTqKZ6gedWAUgaycMdV8Y8h2dRJPvc2wSjckN03pzeoonw8y33uVngfx7WMo1ygdRGEKOT7w== +jest-snapshot@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.3.0.tgz#6e7ea75069dda86e36311a0f73189e830d4f51ad" + integrity sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ== dependencies: "@babel/core" "^7.27.4" "@babel/generator" "^7.27.5" "@babel/plugin-syntax-jsx" "^7.27.1" "@babel/plugin-syntax-typescript" "^7.27.1" "@babel/types" "^7.27.3" - "@jest/expect-utils" "30.0.0" - "@jest/get-type" "30.0.0" - "@jest/snapshot-utils" "30.0.0" - "@jest/transform" "30.0.0" - "@jest/types" "30.0.0" - babel-preset-current-node-syntax "^1.1.0" + "@jest/expect-utils" "30.3.0" + "@jest/get-type" "30.1.0" + "@jest/snapshot-utils" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" + babel-preset-current-node-syntax "^1.2.0" chalk "^4.1.2" - expect "30.0.0" + expect "30.3.0" graceful-fs "^4.2.11" - jest-diff "30.0.0" - jest-matcher-utils "30.0.0" - jest-message-util "30.0.0" - jest-util "30.0.0" - pretty-format "30.0.0" + jest-diff "30.3.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-util "30.3.0" + pretty-format "30.3.0" semver "^7.7.2" synckit "^0.11.8" @@ -6062,6 +6178,18 @@ jest-util@30.0.0: graceful-fs "^4.2.11" picomatch "^4.0.2" +jest-util@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.3.0.tgz#95a4fbacf2dac20e768e2f1744b70519f2ba7980" + integrity sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg== + dependencies: + "@jest/types" "30.3.0" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.3" + jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" @@ -6074,17 +6202,17 @@ jest-util@^29.7.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.0.0.tgz#0e961bcf6ec9922edb10860039529797f02eb821" - integrity sha512-d6OkzsdlWItHAikUDs1hlLmpOIRhsZoXTCliV2XXalVQ3ZOeb9dy0CQ6AKulJu/XOZqpOEr/FiMH+FeOBVV+nw== +jest-validate@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.3.0.tgz#215e11b8fcc5e2ca4b99ea5d730a5b4c969e4355" + integrity sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q== dependencies: - "@jest/get-type" "30.0.0" - "@jest/types" "30.0.0" + "@jest/get-type" "30.1.0" + "@jest/types" "30.3.0" camelcase "^6.3.0" chalk "^4.1.2" leven "^3.1.0" - pretty-format "30.0.0" + pretty-format "30.3.0" jest-validate@^29.6.3, jest-validate@^29.7.0: version "29.7.0" @@ -6098,18 +6226,18 @@ jest-validate@^29.6.3, jest-validate@^29.7.0: leven "^3.1.0" pretty-format "^29.7.0" -jest-watcher@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.0.0.tgz#d444ad4950e20e1cca60e470c448cc15f3f858ce" - integrity sha512-fbAkojcyS53bOL/B7XYhahORq9cIaPwOgd/p9qW/hybbC8l6CzxfWJJxjlPBAIVN8dRipLR0zdhpGQdam+YBtw== +jest-watcher@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.3.0.tgz#3afa1af355b9fe80f0261eb8a23981a315858596" + integrity sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w== dependencies: - "@jest/test-result" "30.0.0" - "@jest/types" "30.0.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" "@types/node" "*" ansi-escapes "^4.3.2" chalk "^4.1.2" emittery "^0.13.1" - jest-util "30.0.0" + jest-util "30.3.0" string-length "^4.0.2" jest-worker@30.0.0: @@ -6123,6 +6251,17 @@ jest-worker@30.0.0: merge-stream "^2.0.0" supports-color "^8.1.1" +jest-worker@30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.3.0.tgz#ae4dc1f1d93d0cba1415624fcedaec40ea764f14" + integrity sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ== + dependencies: + "@types/node" "*" + "@ungap/structured-clone" "^1.3.0" + jest-util "30.3.0" + merge-stream "^2.0.0" + supports-color "^8.1.1" + jest-worker@^29.6.3, jest-worker@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" @@ -6133,15 +6272,15 @@ jest-worker@^29.6.3, jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-30.0.0.tgz#d1d69adb09045053762a40217238c76b19d1db6d" - integrity sha512-/3G2iFwsUY95vkflmlDn/IdLyLWqpQXcftptooaPH4qkyU52V7qVYf1BjmdSPlp1+0fs6BmNtrGaSFwOfV07ew== +jest@^30.3.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-30.3.0.tgz#6460b889dd805e9677400505f16f1d9b14c285a3" + integrity sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg== dependencies: - "@jest/core" "30.0.0" - "@jest/types" "30.0.0" + "@jest/core" "30.3.0" + "@jest/types" "30.3.0" import-local "^3.2.0" - jest-cli "30.0.0" + jest-cli "30.3.0" jiti@2.6.1: version "2.6.1" @@ -7462,6 +7601,11 @@ picomatch@^4.0.2: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== +picomatch@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + pirates@^4.0.4: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" @@ -7526,16 +7670,16 @@ prettier@^3.5.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== -pretty-format@30.0.0: - version "30.0.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.0.tgz#a3137bed442af87eadea2c427a1b201189e590a4" - integrity sha512-18NAOUr4ZOQiIR+BgI5NhQE7uREdx4ZyV0dyay5izh4yfQ+1T7BSvggxvRGoXocrRyevqW5OhScUjbi9GB8R8Q== +pretty-format@30.3.0, pretty-format@^30.0.0: + version "30.3.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.3.0.tgz#e977eed4bcd1b6195faed418af8eac68b9ea1f29" + integrity sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ== dependencies: - "@jest/schemas" "30.0.0" + "@jest/schemas" "30.0.5" ansi-styles "^5.2.0" react-is "^18.3.1" -pretty-format@^29.0.0, pretty-format@^29.0.3, pretty-format@^29.7.0: +pretty-format@^29.0.3, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== From f4735ebb127e0ba2c5950dcb92378350af56dee7 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 12:19:40 +0200 Subject: [PATCH 02/15] fix(store,messagelist): type-annotate empty arrays to fix 'never' inference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With tsconfig.test.json widening the include scope to cover tests and mock-builders, several `const queries = []` / `const whereClause = []` initializers in source files start erroring because TS infers `never[]` and the subsequent `.push(X)` call cannot accept X. These were latent type issues hidden by the base tsconfig's `**/__tests__` exclude; the runtime behavior is unchanged. - addPendingTask/deleteMessage: `queries: PreparedQueries[]` - upsertDraft: `messagesToUpsert: MessageResponseBase[]` (matches the actual type of `draft.quoted_message` / `draft.parent_message`) - appendOrderByClause/appendWhereCluase: `whereClause: string[]` etc. - createCreateTableQuery: explicit `PreparedQueries` return on the `.map` callback so the tuple shape is preserved (was inferring `string[][]`) - useMessageList: `newMessageList: LocalMessage[]` - renderText: drop an unused `// @ts-expect-error` directive (the `react-native-markdown-package` import no longer needs it) Takes the test:typecheck error count from 568 → 556. --- .../Message/MessageItemView/utils/renderText.tsx | 1 - .../components/MessageList/hooks/useMessageList.ts | 2 +- package/src/store/apis/addPendingTask.ts | 3 ++- package/src/store/apis/deleteMessage.ts | 3 ++- package/src/store/apis/upsertDraft.ts | 4 ++-- .../src/store/sqlite-utils/appendOrderByClause.ts | 2 +- package/src/store/sqlite-utils/appendWhereCluase.ts | 2 +- .../src/store/sqlite-utils/createCreateTableQuery.ts | 12 +++++++----- 8 files changed, 16 insertions(+), 13 deletions(-) diff --git a/package/src/components/Message/MessageItemView/utils/renderText.tsx b/package/src/components/Message/MessageItemView/utils/renderText.tsx index 5ebad0f150..7dc5f1dfa0 100644 --- a/package/src/components/Message/MessageItemView/utils/renderText.tsx +++ b/package/src/components/Message/MessageItemView/utils/renderText.tsx @@ -11,7 +11,6 @@ import { } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; -// @ts-expect-error import Markdown from 'react-native-markdown-package'; import Animated, { clamp, scrollTo, useAnimatedRef, useSharedValue } from 'react-native-reanimated'; diff --git a/package/src/components/MessageList/hooks/useMessageList.ts b/package/src/components/MessageList/hooks/useMessageList.ts index 73dac61e24..3ff51c61a9 100644 --- a/package/src/components/MessageList/hooks/useMessageList.ts +++ b/package/src/components/MessageList/hooks/useMessageList.ts @@ -29,7 +29,7 @@ export const useMessageList = (params: UseMessageListParams) => { const messageList = threadList ? threadMessages : messages; const processedMessageList = useMemo(() => { - const newMessageList = []; + const newMessageList: LocalMessage[] = []; for (const message of messageList) { if (isFlashList) { newMessageList.push(message); diff --git a/package/src/store/apis/addPendingTask.ts b/package/src/store/apis/addPendingTask.ts index 28b141e691..8aabf100fe 100644 --- a/package/src/store/apis/addPendingTask.ts +++ b/package/src/store/apis/addPendingTask.ts @@ -4,6 +4,7 @@ import { mapTaskToStorable } from '../mappers/mapTaskToStorable'; import { createDeleteQuery } from '../sqlite-utils/createDeleteQuery'; import { createUpsertQuery } from '../sqlite-utils/createUpsertQuery'; import { SqliteClient } from '../SqliteClient'; +import type { PreparedQueries } from '../types'; /* * addPendingTask - Adds a pending task to the database @@ -15,7 +16,7 @@ import { SqliteClient } from '../SqliteClient'; export const addPendingTask = async (task: PendingTask) => { const storable = mapTaskToStorable(task); const { channelId, channelType, threadId, payload, type } = storable; - const queries = []; + const queries: PreparedQueries[] = []; if (type === 'create-draft' || type === 'delete-draft') { // Only one draft pending task is allowed per entity (i.e thread, channel etc). // If multiple arrive, we'll simply take the last one (since deleteDraft does not diff --git a/package/src/store/apis/deleteMessage.ts b/package/src/store/apis/deleteMessage.ts index ac8264cd74..02fd974038 100644 --- a/package/src/store/apis/deleteMessage.ts +++ b/package/src/store/apis/deleteMessage.ts @@ -1,8 +1,9 @@ import { createDeleteQuery } from '../sqlite-utils/createDeleteQuery'; import { SqliteClient } from '../SqliteClient'; +import type { PreparedQueries } from '../types'; export const deleteMessage = async ({ execute = true, id }: { id: string; execute?: boolean }) => { - const queries = []; + const queries: PreparedQueries[] = []; queries.push( createDeleteQuery('messages', { diff --git a/package/src/store/apis/upsertDraft.ts b/package/src/store/apis/upsertDraft.ts index b774b0ff6e..f6f81f830a 100644 --- a/package/src/store/apis/upsertDraft.ts +++ b/package/src/store/apis/upsertDraft.ts @@ -1,4 +1,4 @@ -import { DraftResponse } from 'stream-chat'; +import type { DraftResponse, MessageResponseBase } from 'stream-chat'; import { upsertMessages } from './upsertMessages'; @@ -40,7 +40,7 @@ export const upsertDraft = async ({ draftMessage: storableDraftMessage, }); - const messagesToUpsert = []; + const messagesToUpsert: MessageResponseBase[] = []; if (draft.quoted_message) { messagesToUpsert.push(draft.quoted_message); diff --git a/package/src/store/sqlite-utils/appendOrderByClause.ts b/package/src/store/sqlite-utils/appendOrderByClause.ts index 5c8093f298..3e5918b5e0 100644 --- a/package/src/store/sqlite-utils/appendOrderByClause.ts +++ b/package/src/store/sqlite-utils/appendOrderByClause.ts @@ -9,7 +9,7 @@ export const appendOrderByClause = ( return [selectQuery, []]; } - const orderByClause = []; + const orderByClause: string[] = []; for (const key in orderBy) { const order = orderBy[key]; diff --git a/package/src/store/sqlite-utils/appendWhereCluase.ts b/package/src/store/sqlite-utils/appendWhereCluase.ts index 03f5ac82e7..658e1a7012 100644 --- a/package/src/store/sqlite-utils/appendWhereCluase.ts +++ b/package/src/store/sqlite-utils/appendWhereCluase.ts @@ -9,7 +9,7 @@ export const appendWhereClause = ( return [selectQuery, []]; } - const whereClause = []; + const whereClause: string[] = []; const whereParams: TableColumnValue[] = []; for (const key in whereCondition) { diff --git a/package/src/store/sqlite-utils/createCreateTableQuery.ts b/package/src/store/sqlite-utils/createCreateTableQuery.ts index 01fdc7f72f..f05e972069 100644 --- a/package/src/store/sqlite-utils/createCreateTableQuery.ts +++ b/package/src/store/sqlite-utils/createCreateTableQuery.ts @@ -20,11 +20,13 @@ export const createCreateTableQuery = (tableName: Table): PreparedQueries[] => { ) || []; const indexQueries: PreparedQueries[] = - tables[tableName].indexes?.map((index) => [ - `CREATE ${index.unique ? 'UNIQUE' : ''} INDEX IF NOT EXISTS ${ - index.name - } ON ${tableName}(${index.columns.join(',')})`, - ]) || []; + tables[tableName].indexes?.map( + (index): PreparedQueries => [ + `CREATE ${index.unique ? 'UNIQUE' : ''} INDEX IF NOT EXISTS ${ + index.name + } ON ${tableName}(${index.columns.join(',')})`, + ], + ) || []; return [ [ From 7c359e98ac1a1f837c369ce3f6181332341ec7f9 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 12:29:38 +0200 Subject: [PATCH 03/15] refactor(mock-builders): tighten types in generators and event dispatchers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - generator/channel: narrow `id`/`type` to `string` via `??` defaults before composing the object; add missing `reminders` on default config; tag `commands[0].name/set` as `'giphy' as const` / `'fun_set' as const` to satisfy `CommandVariants`; drop `type` from nested `config` (not on `ChannelConfigFields`). - generator/attachment: drop `id` and `description` fields that aren't on `Attachment` (verified no caller uses them). - generator/message, generator/reaction, event/messageRead, event/notificationMarkUnread: keep `Date` objects at runtime (a handful of components call `.toDateString()` on them) but silence the `Date` vs `string` mismatch with `as unknown as string` where the underlying `MessageResponse`/`Event` type demands a string. Converting to ISO strings would have been cleaner but broke runtime; see NOTE comment in generator/message. - mock: type `user`/`_user` as `OwnUserResponse` so `mutes: []` type- checks; replace the `as { mutes?: unknown[] }` workaround. - api/channelMocks: drop bogus `message: {}` field from `FORMATTED_MESSAGE` (not on `LocalMessage`); add required `deleted_at: null`. - api/initiateClientWithChannels: pass the top-level `id`/`type` from the generated channel to `client.channel(...)` (both `string`) instead of the nested `channel.id`/`channel.type` (`string | undefined`). - DB/mock: narrow `db.pragma()`'s `unknown` return to `unknown[]`. Takes test:typecheck from 556 → 544. Full test suite: same pre- existing SQLite-isolation flake in offline-support; no regressions. --- package/src/mock-builders/DB/mock.ts | 2 +- .../src/mock-builders/api/channelMocks.tsx | 2 +- .../api/initiateClientWithChannels.ts | 2 +- .../src/mock-builders/event/messageRead.ts | 2 +- .../event/notificationMarkUnread.ts | 2 +- .../src/mock-builders/generator/attachment.ts | 3 -- .../src/mock-builders/generator/channel.ts | 48 ++++++++++--------- .../src/mock-builders/generator/message.ts | 8 +++- .../src/mock-builders/generator/reaction.ts | 2 +- package/src/mock-builders/mock.ts | 10 ++-- 10 files changed, 42 insertions(+), 39 deletions(-) diff --git a/package/src/mock-builders/DB/mock.ts b/package/src/mock-builders/DB/mock.ts index ae06565dda..faa2112f14 100644 --- a/package/src/mock-builders/DB/mock.ts +++ b/package/src/mock-builders/DB/mock.ts @@ -36,7 +36,7 @@ export const sqliteMock = { if (pragmaQueryTokens[2] === '=') { db.pragma(`${pragmaQueryTokens[1]} = ${pragmaQueryTokens[3]}`); } else { - result = db.pragma(`${pragmaQueryTokens[1]}`); + result = db.pragma(`${pragmaQueryTokens[1]}`) as unknown[]; } return { diff --git a/package/src/mock-builders/api/channelMocks.tsx b/package/src/mock-builders/api/channelMocks.tsx index 9c41c63fe1..c2b8c59891 100644 --- a/package/src/mock-builders/api/channelMocks.tsx +++ b/package/src/mock-builders/api/channelMocks.tsx @@ -143,8 +143,8 @@ const LATEST_MESSAGE = { const FORMATTED_MESSAGE: LocalMessage = { created_at: new Date('2021-02-12T12:12:35.862282Z'), + deleted_at: null, id: '', - message: {} as unknown as MessageResponse, pinned_at: new Date('2021-02-12T12:12:35.862282Z'), status: 'received', type: 'regular', diff --git a/package/src/mock-builders/api/initiateClientWithChannels.ts b/package/src/mock-builders/api/initiateClientWithChannels.ts index 334f239d12..85198a9a3a 100644 --- a/package/src/mock-builders/api/initiateClientWithChannels.ts +++ b/package/src/mock-builders/api/initiateClientWithChannels.ts @@ -25,7 +25,7 @@ const initChannelFromData = async ({ }); useMockedApis(client, [getOrCreateChannelApi(mockedChannelData)]); - const channel = client.channel(mockedChannelData.channel.type, mockedChannelData.channel.id); + const channel = client.channel(mockedChannelData.type, mockedChannelData.id); await channel.watch(); jest.spyOn(channel, 'getConfig').mockImplementation(() => mockedChannelData.channel.config); // jest diff --git a/package/src/mock-builders/event/messageRead.ts b/package/src/mock-builders/event/messageRead.ts index 00db7f92c5..7de4293e86 100644 --- a/package/src/mock-builders/event/messageRead.ts +++ b/package/src/mock-builders/event/messageRead.ts @@ -7,7 +7,7 @@ export default ( channel: Partial = {}, payload: Partial = {}, ): Event => { - const newDate = new Date(); + const newDate = new Date() as unknown as string; const event = fromPartial({ channel, cid: channel.cid, diff --git a/package/src/mock-builders/event/notificationMarkUnread.ts b/package/src/mock-builders/event/notificationMarkUnread.ts index 424da94ea4..8bf3dd9e17 100644 --- a/package/src/mock-builders/event/notificationMarkUnread.ts +++ b/package/src/mock-builders/event/notificationMarkUnread.ts @@ -7,7 +7,7 @@ export default ( payload: Partial = {}, user: Partial = {}, ) => { - const newDate = new Date(); + const newDate = new Date() as unknown as string; client.dispatchEvent( fromPartial({ channel, diff --git a/package/src/mock-builders/generator/attachment.ts b/package/src/mock-builders/generator/attachment.ts index c55c03ca7c..a032e8cd7e 100644 --- a/package/src/mock-builders/generator/attachment.ts +++ b/package/src/mock-builders/generator/attachment.ts @@ -21,7 +21,6 @@ export const generateVideoAttachment = (a?: Partial): Attachment => }); export const generateImageAttachment = (a?: Partial): Attachment => ({ - id: uuidv4(), image_url: uuidv4(), title: uuidv4(), type: 'image', @@ -45,7 +44,6 @@ export const generateImageUploadPreview = (a?: Partial): UploadPr export const generateAudioAttachment = (a?: Partial): Attachment => ({ asset_url: 'http://www.jackblack.com/tribute.mp3', - description: uuidv4(), image_url, text: uuidv4(), title: uuidv4(), @@ -55,7 +53,6 @@ export const generateAudioAttachment = (a?: Partial): Attachment => export const generateFileAttachment = (a?: Partial): Attachment => ({ asset_url: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', - description: uuidv4(), file_size: 1337, mime_type: uuidv4(), text: uuidv4(), diff --git a/package/src/mock-builders/generator/channel.ts b/package/src/mock-builders/generator/channel.ts index 3a254c49b8..964ee857ec 100644 --- a/package/src/mock-builders/generator/channel.ts +++ b/package/src/mock-builders/generator/channel.ts @@ -34,8 +34,8 @@ const defaultConfig = { { args: '[text]', description: 'Post a random gif to the channel', - name: 'giphy', - set: 'fun_set', + name: 'giphy' as const, + set: 'fun_set' as const, }, ], connect_events: true, @@ -46,6 +46,7 @@ const defaultConfig = { name: 'messaging', reactions: true, read_events: true, + reminders: false, replies: true, search: true, typing_events: true, @@ -71,31 +72,32 @@ export type GeneratedChannel = { type GeneratedChannelIdType = { id?: string; type?: string }; -const getChannelDefaults = ( - { id, type }: GeneratedChannelIdType = { id: uuidv4(), type: 'messaging' }, -): GeneratedChannel => ({ - _client: {}, - channel: { - cid: `${type}:${id}`, - config: { - ...defaultConfig, - name: type, +const getChannelDefaults = (opts: GeneratedChannelIdType = {}): GeneratedChannel => { + const id = opts.id ?? uuidv4(); + const type = opts.type ?? 'messaging'; + return { + _client: {}, + channel: { + cid: `${type}:${id}`, + config: { + ...defaultConfig, + name: type, + }, + created_at: '2020-04-28T11:20:48.578147Z', + created_by: getUserDefaults(), + frozen: false, + id, + own_capabilities: defaultCapabilities, type, + updated_at: '2020-04-28T11:20:48.578147Z', }, - created_at: '2020-04-28T11:20:48.578147Z', - created_by: getUserDefaults(), - frozen: false, + cid: `${type}:${id}`, id, - own_capabilities: defaultCapabilities, + messages: [], + state: defaultState, type, - updated_at: '2020-04-28T11:20:48.578147Z', - }, - cid: `${type}:${id}`, - id: id as string, - messages: [], - state: defaultState, - type: type as string, -}); + }; +}; export const generateChannel = ( customValues: Partial & Record = {}, diff --git a/package/src/mock-builders/generator/message.ts b/package/src/mock-builders/generator/message.ts index c5edd6058f..16010b9826 100644 --- a/package/src/mock-builders/generator/message.ts +++ b/package/src/mock-builders/generator/message.ts @@ -10,12 +10,16 @@ export const generateMessage = (options: GenerateMessageOptions = {}): MessageRe const timestamp = options.timestamp || new Date(new Date().getTime() - Math.floor(Math.random() * 100000)); + // NOTE: `created_at` / `updated_at` on `MessageResponse` are typed as `string`, + // but tests here treat the generated message as if it were a `LocalMessage` + // (where those fields are `Date`). Keeping `Date` objects at runtime preserves + // behavior of component code that calls e.g. `.toDateString()` on them. return fromPartial({ attachments: [], - created_at: timestamp, + created_at: timestamp as unknown as string, html: '

regular

', id: uuidv4(), - message_text_updated_at: timestamp, + message_text_updated_at: timestamp as unknown as string, text: uuidv4(), type: 'regular', updated_at: timestamp.toString(), diff --git a/package/src/mock-builders/generator/reaction.ts b/package/src/mock-builders/generator/reaction.ts index 7f6420e016..3d4b692a4f 100644 --- a/package/src/mock-builders/generator/reaction.ts +++ b/package/src/mock-builders/generator/reaction.ts @@ -6,7 +6,7 @@ import { generateUser } from './user'; export const generateReaction = (options: Partial = {}): ReactionResponse => { const user = options.user || generateUser(); return fromPartial({ - created_at: new Date(), + created_at: new Date() as unknown as string, type: 'love', user, user_id: user.id, diff --git a/package/src/mock-builders/mock.ts b/package/src/mock-builders/mock.ts index c228e335f7..8af9ad3fa7 100644 --- a/package/src/mock-builders/mock.ts +++ b/package/src/mock-builders/mock.ts @@ -1,7 +1,7 @@ /* eslint no-underscore-dangle: 0 */ /* eslint no-param-reassign: 0 */ -import { StreamChat, type UserResponse } from 'stream-chat'; +import { StreamChat, type OwnUserResponse, type UserResponse } from 'stream-chat'; const apiKey = 'API_KEY'; const token = 'dummy_token'; @@ -12,7 +12,8 @@ type MockClientOptions = { disableAppSettings?: boolean }; // authenticated client without going through the real network handshake. type MockableStreamChat = StreamChat & { connectionId?: string; - _user?: UserResponse; + user?: OwnUserResponse; + _user?: OwnUserResponse; userToken?: string; setUser?: (user: UserResponse) => Promise; wsPromise?: Promise; @@ -24,9 +25,8 @@ export const setUser = (client: StreamChat, user: UserResponse): Promise = new Promise((resolve) => { const c = client as MockableStreamChat; c.connectionId = 'dumm_connection_id'; - c.user = user; - if (c.user) (c.user as { mutes?: unknown[] }).mutes = []; - c._user = { ...user }; + c.user = { ...user, mutes: [] } as unknown as OwnUserResponse; + c._user = { ...c.user }; c.userID = user.id; c.userToken = token; resolve(); From 0be246adaf9dc8656040e7097323bbedb5395ebb Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 12:42:33 +0200 Subject: [PATCH 04/15] refactor(tests): annotate pre-existing TS tests exposed by tsconfig.test.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These test files were always `.test.ts`/`.test.tsx` but errors were hidden because the base `tsconfig.json` excludes `**/__tests__`. The migration added `tsconfig.test.json` which now checks them. Behavior unchanged — only type annotations and casts. - state-store/__tests__/image-gallery-state-store.test.ts: add local `toLocalMessage` / `toLocalMessages` helpers that cast `MessageResponse` mocks to `LocalMessage` at assignment sites (store expects `LocalMessage[]`). Convert numeric `id` fields to strings (matches the `MessageResponse['id']: string` signature). Convert Giphy `height`/`width` to strings (`GiphyVersionInfo` types them as string). Narrow `tDateTimeParser()` call results with `as Dayjs.Dayjs` (the returned union type `TDateTimeParserOutput` doesn't expose `.locale`/ `.localeData`). Fixes all 82 errors. - utils/__tests__/Streami18n.test.ts: import `Moment` type; narrow `tDateTimeParser()` outputs with `as Dayjs.Dayjs` / `as Moment` before calling `.locale()`/`.format()`; widen inline options types where the test-only `abc` translation key collides with the `Partial< enTranslations>` type; access private `i18n.init()` via a minimal `{ init: () => Promise }` cast. Fixes all 19 errors. - store/apis/__tests__/updatePendingTask.test.ts: import `PendingTask` from `stream-chat`; cast the test payload shape to `PendingTask` (the local union doesn't include a `send-message` variant that matches `[MessageResponse, {}]`). Type `BetterSqlite.selectFromTable` rows via its generic arg. Narrow `updatedTask.payload[0].text` access with a tuple cast. Fixes all 6 errors. Test suite: all three files still pass (unchanged assertions). Total test:typecheck count: 544 → 437 (−107). --- .../image-gallery-state-store.test.ts | 181 ++++++++++-------- .../apis/__tests__/updatePendingTask.test.ts | 23 ++- .../src/utils/__tests__/Streami18n.test.ts | 74 ++++--- 3 files changed, 162 insertions(+), 116 deletions(-) diff --git a/package/src/state-store/__tests__/image-gallery-state-store.test.ts b/package/src/state-store/__tests__/image-gallery-state-store.test.ts index e6b7c9fd9a..901bb23577 100644 --- a/package/src/state-store/__tests__/image-gallery-state-store.test.ts +++ b/package/src/state-store/__tests__/image-gallery-state-store.test.ts @@ -31,16 +31,19 @@ const { isVideoPlayerAvailable } = jest.requireMock('../../native') as { const createGiphyAttachment = (overrides: Partial = {}): Attachment => ({ giphy: { fixed_height: { - height: 200, + height: '200', url: 'https://giphy.com/test.gif', - width: 200, + width: '200', }, - }, + } as unknown as Attachment['giphy'], thumb_url: 'https://giphy.com/thumb.gif', type: 'giphy', ...overrides, }); +const toLocalMessage = (msg: unknown): LocalMessage => msg as LocalMessage; +const toLocalMessages = (msgs: unknown[]): LocalMessage[] => msgs as LocalMessage[]; + describe('ImageGalleryStateStore', () => { beforeEach(() => { jest.clearAllMocks(); @@ -103,18 +106,18 @@ describe('ImageGalleryStateStore', () => { describe('messages getter and setter', () => { it('should get messages from state', () => { const store = new ImageGalleryStateStore(); - const messages = [generateMessage({ id: 1 }), generateMessage({ id: 2 })]; + const messages = [generateMessage({ id: '1' }), generateMessage({ id: '2' })]; - store.messages = messages; + store.messages = toLocalMessages(messages); expect(store.messages).toEqual(messages); }); it('should update state when setting messages', () => { const store = new ImageGalleryStateStore(); - const messages = [generateMessage({ id: 1 })]; + const messages = [generateMessage({ id: '1' })]; - store.messages = messages; + store.messages = toLocalMessages(messages); expect(store.state.getLatestValue().messages).toEqual(messages); }); @@ -192,9 +195,9 @@ describe('ImageGalleryStateStore', () => { const imageAttachment = generateImageAttachment({ image_url: 'https://example.com/image.jpg', }); - const message = generateMessage({ attachments: [imageAttachment], id: 1 }); + const message = generateMessage({ attachments: [imageAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(1); expect(store.attachmentsWithMessage[0].attachments).toContain(imageAttachment); @@ -205,9 +208,9 @@ describe('ImageGalleryStateStore', () => { const videoAttachment = generateVideoAttachment({ asset_url: 'https://example.com/video.mp4', }); - const message = generateMessage({ attachments: [videoAttachment], id: 1 }); + const message = generateMessage({ attachments: [videoAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(1); expect(store.attachmentsWithMessage[0].attachments).toContain(videoAttachment); @@ -216,9 +219,9 @@ describe('ImageGalleryStateStore', () => { it('should filter messages with giphy attachments', () => { const store = new ImageGalleryStateStore(); const giphyAttachment = createGiphyAttachment(); - const message = generateMessage({ attachments: [giphyAttachment], id: 1 }); + const message = generateMessage({ attachments: [giphyAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(1); expect(store.attachmentsWithMessage[0].attachments).toContain(giphyAttachment); @@ -230,9 +233,9 @@ describe('ImageGalleryStateStore', () => { const videoAttachment = generateVideoAttachment({ asset_url: 'https://example.com/video.mp4', }); - const message = generateMessage({ attachments: [videoAttachment], id: 1 }); + const message = generateMessage({ attachments: [videoAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -243,9 +246,9 @@ describe('ImageGalleryStateStore', () => { image_url: 'https://example.com/image.jpg', title_link: 'https://example.com', }); - const message = generateMessage({ attachments: [linkPreviewAttachment], id: 1 }); + const message = generateMessage({ attachments: [linkPreviewAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -256,9 +259,9 @@ describe('ImageGalleryStateStore', () => { image_url: 'https://example.com/image.jpg', og_scrape_url: 'https://example.com', }); - const message = generateMessage({ attachments: [linkAttachment], id: 1 }); + const message = generateMessage({ attachments: [linkAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -270,9 +273,9 @@ describe('ImageGalleryStateStore', () => { image_url: 'https://example.com/preview.jpg', title_link: 'https://example.com', }); - const message = generateMessage({ attachments: [viewableImage, linkPreview], id: 1 }); + const message = generateMessage({ attachments: [viewableImage, linkPreview], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(1); }); @@ -283,27 +286,27 @@ describe('ImageGalleryStateStore', () => { asset_url: 'https://example.com/file.pdf', type: 'file', }; - const message = generateMessage({ attachments: [fileAttachment], id: 1 }); + const message = generateMessage({ attachments: [fileAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(0); }); it('should handle null attachments gracefully', () => { const store = new ImageGalleryStateStore(); - const message = generateMessage({ attachments: [null as unknown as Attachment], id: 1 }); + const message = generateMessage({ attachments: [null as unknown as Attachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(0); }); it('should handle messages without attachments array', () => { const store = new ImageGalleryStateStore(); - const message = generateMessage({ attachments: undefined, id: 1 }); + const message = generateMessage({ attachments: undefined, id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -340,7 +343,7 @@ describe('ImageGalleryStateStore', () => { original_width: 800, thumb_url: 'https://example.com/thumb.jpg', }); - const user: Partial = { id: 'user-1', name: 'Test User' }; + const user: UserResponse = { id: 'user-1', name: 'Test User' } as UserResponse; const message = generateMessage({ attachments: [imageAttachment], cid: 'channel-msg-1', @@ -349,7 +352,7 @@ describe('ImageGalleryStateStore', () => { user_id: user.id, }); - store.messages = [message]; + store.messages = toLocalMessages([message]); const assets = store.assets; expect(assets).toHaveLength(1); @@ -372,9 +375,9 @@ describe('ImageGalleryStateStore', () => { asset_url: 'https://example.com/video.mp4', thumb_url: 'https://example.com/video-thumb.jpg', }); - const message = generateMessage({ attachments: [videoAttachment], id: 1 }); + const message = generateMessage({ attachments: [videoAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); const assets = store.assets; expect(assets).toHaveLength(1); @@ -388,9 +391,9 @@ describe('ImageGalleryStateStore', () => { it('should transform giphy attachments with correct mime type', () => { const store = new ImageGalleryStateStore(); const giphyAttachment = createGiphyAttachment(); - const message = generateMessage({ attachments: [giphyAttachment], id: 1 }); + const message = generateMessage({ attachments: [giphyAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); const assets = store.assets; expect(assets).toHaveLength(1); @@ -405,9 +408,9 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const attachment1 = generateImageAttachment({ image_url: 'https://example.com/image1.jpg' }); const attachment2 = generateImageAttachment({ image_url: 'https://example.com/image2.jpg' }); - const message = generateMessage({ attachments: [attachment1, attachment2], id: 1 }); + const message = generateMessage({ attachments: [attachment1, attachment2], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); const assets = store.assets; expect(assets).toHaveLength(2); @@ -418,14 +421,14 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore({ giphyVersion: 'original' }); const giphyAttachment: Attachment = { giphy: { - fixed_height: { height: 200, url: 'https://giphy.com/fixed.gif', width: 200 }, - original: { height: 400, url: 'https://giphy.com/original.gif', width: 400 }, - }, + fixed_height: { height: '200', url: 'https://giphy.com/fixed.gif', width: '200' }, + original: { height: '400', url: 'https://giphy.com/original.gif', width: '400' }, + } as unknown as Attachment['giphy'], type: 'giphy', }; - const message = generateMessage({ attachments: [giphyAttachment], id: 1 }); + const message = generateMessage({ attachments: [giphyAttachment], id: '1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(getUrlOfImageAttachment(giphyAttachment, 'original')).toBe( 'https://giphy.com/original.gif', @@ -439,14 +442,14 @@ describe('ImageGalleryStateStore', () => { generateImageAttachment({ image_url: 'https://example.com/image1.jpg' }), generateImageAttachment({ image_url: 'https://example.com/image2.jpg' }), ], - id: 1, + id: '1', }); const message2 = generateMessage({ attachments: [generateVideoAttachment({ asset_url: 'https://example.com/video.mp4' })], - id: 2, + id: '2', }); - store.messages = [message1, message2]; + store.messages = toLocalMessages([message1, message2]); expect(store.assets).toHaveLength(3); }); @@ -458,8 +461,8 @@ describe('ImageGalleryStateStore', () => { const initialMessages = [generateMessage({ id: 'msg-1' })]; const newMessages = [generateMessage({ id: 'msg-2' }), generateMessage({ id: 'msg-3' })]; - store.messages = initialMessages; - store.appendMessages(newMessages); + store.messages = toLocalMessages(initialMessages); + store.appendMessages(toLocalMessages(newMessages)); expect(store.messages).toHaveLength(3); expect(store.messages).toEqual([...initialMessages, ...newMessages]); @@ -469,7 +472,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const newMessages = [generateMessage({ id: 'msg-1' })]; - store.appendMessages(newMessages); + store.appendMessages(toLocalMessages(newMessages)); expect(store.messages).toEqual(newMessages); }); @@ -478,7 +481,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const initialMessages = [generateMessage({ id: 'msg-1' })]; - store.messages = initialMessages; + store.messages = toLocalMessages(initialMessages); store.appendMessages([]); expect(store.messages).toEqual(initialMessages); @@ -492,8 +495,8 @@ describe('ImageGalleryStateStore', () => { const message2 = generateMessage({ id: 'msg-2' }); const message3 = generateMessage({ id: 'msg-3' }); - store.messages = [message1, message2, message3]; - store.removeMessages([message2]); + store.messages = toLocalMessages([message1, message2, message3]); + store.removeMessages([toLocalMessage(message2)]); expect(store.messages).toHaveLength(2); expect(store.messages).toEqual([message1, message3]); @@ -505,8 +508,8 @@ describe('ImageGalleryStateStore', () => { const message2 = generateMessage({ id: 'msg-2' }); const message3 = generateMessage({ id: 'msg-3' }); - store.messages = [message1, message2, message3]; - store.removeMessages([message1, message3]); + store.messages = toLocalMessages([message1, message2, message3]); + store.removeMessages(toLocalMessages([message1, message3])); expect(store.messages).toHaveLength(1); expect(store.messages).toEqual([message2]); @@ -518,8 +521,8 @@ describe('ImageGalleryStateStore', () => { const message2 = generateMessage({ id: 'msg-2' }); const nonExistentMessage = generateMessage({ id: 'non-existent' }); - store.messages = [message1, message2]; - store.removeMessages([nonExistentMessage]); + store.messages = toLocalMessages([message1, message2]); + store.removeMessages([toLocalMessage(nonExistentMessage)]); expect(store.messages).toHaveLength(2); expect(store.messages).toEqual([message1, message2]); @@ -529,7 +532,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const message1 = generateMessage({ id: 'msg-1' }); - store.messages = [message1]; + store.messages = toLocalMessages([message1]); store.removeMessages([]); expect(store.messages).toEqual([message1]); @@ -547,7 +550,10 @@ describe('ImageGalleryStateStore', () => { ]; const selectedUrl = 'https://example.com/1.jpg'; - store.openImageGallery({ messages, selectedAttachmentUrl: selectedUrl }); + store.openImageGallery({ + messages: toLocalMessages(messages), + selectedAttachmentUrl: selectedUrl, + }); expect(store.messages).toEqual(messages); expect(store.selectedAttachmentUrl).toBe(selectedUrl); @@ -557,7 +563,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const messages = [generateMessage({ id: 'msg-1' })]; - store.openImageGallery({ messages }); + store.openImageGallery({ messages: toLocalMessages(messages) }); expect(store.messages).toEqual(messages); expect(store.selectedAttachmentUrl).toBeUndefined(); @@ -568,8 +574,8 @@ describe('ImageGalleryStateStore', () => { const oldMessages = [generateMessage({ id: 'msg-1' })]; const newMessages = [generateMessage({ id: 'msg-2' })]; - store.messages = oldMessages; - store.openImageGallery({ messages: newMessages }); + store.messages = toLocalMessages(oldMessages); + store.openImageGallery({ messages: toLocalMessages(newMessages) }); expect(store.messages).toEqual(newMessages); }); @@ -584,7 +590,7 @@ describe('ImageGalleryStateStore', () => { attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.state.getLatestValue().assets).toHaveLength(1); @@ -604,20 +610,22 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const unsubscribe = store.subscribeToMessages(); - store.messages = [ + store.messages = toLocalMessages([ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]; + ]); expect(store.state.getLatestValue().assets).toHaveLength(1); - store.appendMessages([ - generateMessage({ - attachments: [generateImageAttachment({ image_url: 'https://example.com/2.jpg' })], - id: 'msg-2', - }), - ]); + store.appendMessages( + toLocalMessages([ + generateMessage({ + attachments: [generateImageAttachment({ image_url: 'https://example.com/2.jpg' })], + id: 'msg-2', + }), + ]), + ); expect(store.state.getLatestValue().assets).toHaveLength(2); unsubscribe(); @@ -645,7 +653,7 @@ describe('ImageGalleryStateStore', () => { }), ]; - store.messages = messages; + store.messages = toLocalMessages(messages); store.selectedAttachmentUrl = 'https://example.com/2.jpg'; expect(store.state.getLatestValue().currentIndex).toBe(1); @@ -658,12 +666,12 @@ describe('ImageGalleryStateStore', () => { store.subscribeToMessages(); const unsubscribe = store.subscribeToSelectedAttachmentUrl(); - store.messages = [ + store.messages = toLocalMessages([ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]; + ]); store.selectedAttachmentUrl = 'https://example.com/non-existent.jpg'; expect(store.state.getLatestValue().currentIndex).toBe(0); @@ -688,14 +696,14 @@ describe('ImageGalleryStateStore', () => { store.subscribeToMessages(); const unsubscribe = store.subscribeToSelectedAttachmentUrl(); - store.messages = [ + store.messages = toLocalMessages([ generateMessage({ attachments: [ generateImageAttachment({ image_url: 'https://example.com/image.jpg?size=small' }), ], id: 'msg-1', }), - ]; + ]); store.selectedAttachmentUrl = 'https://example.com/image.jpg?size=large'; expect(store.state.getLatestValue().currentIndex).toBe(0); @@ -719,12 +727,12 @@ describe('ImageGalleryStateStore', () => { const unsubscribe = store.registerSubscriptions(); // Test that message subscription is working - store.messages = [ + store.messages = toLocalMessages([ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]; + ]); expect(store.state.getLatestValue().assets).toHaveLength(1); // Test that selectedAttachmentUrl subscription is working @@ -759,12 +767,12 @@ describe('ImageGalleryStateStore', () => { describe('clear', () => { it('should reset state to initial values', () => { const store = new ImageGalleryStateStore(); - store.messages = [ + store.messages = toLocalMessages([ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]; + ]); store.selectedAttachmentUrl = 'https://example.com/1.jpg'; store.currentIndex = 5; @@ -795,9 +803,9 @@ describe('ImageGalleryStateStore', () => { id: 'msg-1', }), user: undefined, - } as LocalMessage; + } as unknown as LocalMessage; - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.assets).toHaveLength(1); expect(store.assets[0].user).toBeUndefined(); @@ -807,8 +815,11 @@ describe('ImageGalleryStateStore', () => { const store1 = new ImageGalleryStateStore({ autoPlayVideo: true }); const store2 = new ImageGalleryStateStore({ autoPlayVideo: false }); - store1.messages = [generateMessage({ id: 'msg-1' })]; - store2.messages = [generateMessage({ id: 'msg-2' }), generateMessage({ id: 'msg-3' })]; + store1.messages = toLocalMessages([generateMessage({ id: 'msg-1' })]); + store2.messages = toLocalMessages([ + generateMessage({ id: 'msg-2' }), + generateMessage({ id: 'msg-3' }), + ]); expect(store1.messages).toHaveLength(1); expect(store2.messages).toHaveLength(2); @@ -821,14 +832,14 @@ describe('ImageGalleryStateStore', () => { store.subscribeToMessages(); for (let i = 0; i < 100; i++) { - store.messages = [ + store.messages = toLocalMessages([ generateMessage({ attachments: [ generateImageAttachment({ image_url: `https://example.com/image-${i}.jpg` }), ], id: `msg-${i}`, }), - ]; + ]); } expect(store.state.getLatestValue().assets).toHaveLength(1); @@ -839,7 +850,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const message = generateMessage({ attachments: [], id: 'msg-1' }); - store.messages = [message]; + store.messages = toLocalMessages([message]); expect(store.attachmentsWithMessage).toHaveLength(0); expect(store.assets).toEqual([]); @@ -862,7 +873,7 @@ describe('ImageGalleryStateStore', () => { }), ]; - store.messages = messages; + store.messages = toLocalMessages(messages); const assets = store.assets; expect(assets[0].uri).toBe('https://example.com/first.jpg'); @@ -882,7 +893,7 @@ describe('ImageGalleryStateStore', () => { ); const newMessages = [generateMessage({ id: 'msg-1' })]; - store.messages = newMessages; + store.messages = toLocalMessages(newMessages); expect(callback).toHaveBeenCalledWith(newMessages); }); diff --git a/package/src/store/apis/__tests__/updatePendingTask.test.ts b/package/src/store/apis/__tests__/updatePendingTask.test.ts index 4b12803372..9029a8bd87 100644 --- a/package/src/store/apis/__tests__/updatePendingTask.test.ts +++ b/package/src/store/apis/__tests__/updatePendingTask.test.ts @@ -1,3 +1,4 @@ +import type { PendingTask } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; import { addPendingTask, getPendingTasks, updatePendingTask } from '..'; @@ -31,9 +32,14 @@ describe('updatePendingTask', () => { messageId: originalMessage.id, payload: [originalMessage, {}], type: 'send-message', - }); + } as unknown as PendingTask); - const [originalRow] = await BetterSqlite.selectFromTable('pendingTasks'); + const [originalRow] = await BetterSqlite.selectFromTable<{ + id: number; + createdAt: string; + type: string; + payload: string; + }>('pendingTasks'); const [originalTask] = await getPendingTasks({ messageId: originalMessage.id }); const editedMessage = { @@ -42,17 +48,22 @@ describe('updatePendingTask', () => { }; await updatePendingTask({ - id: originalTask.id, + id: originalTask.id as number, task: { channelId, channelType: 'messaging', messageId: originalMessage.id, payload: [editedMessage, {}], type: 'send-message', - }, + } as unknown as PendingTask, }); - const [updatedRow] = await BetterSqlite.selectFromTable('pendingTasks'); + const [updatedRow] = await BetterSqlite.selectFromTable<{ + id: number; + createdAt: string; + type: string; + payload: string; + }>('pendingTasks'); const [updatedTask] = await getPendingTasks({ messageId: originalMessage.id }); expect(updatedRow.id).toBe(originalRow.id); @@ -61,6 +72,6 @@ describe('updatePendingTask', () => { expect(JSON.parse(updatedRow.payload)[0].text).toBe('edited text'); expect(updatedTask.id).toBe(originalTask.id); expect(updatedTask.type).toBe('send-message'); - expect(updatedTask.payload[0].text).toBe('edited text'); + expect((updatedTask.payload as [{ text: string }, object])[0].text).toBe('edited text'); }); }); diff --git a/package/src/utils/__tests__/Streami18n.test.ts b/package/src/utils/__tests__/Streami18n.test.ts index a3152bf09a..bd3f733252 100644 --- a/package/src/utils/__tests__/Streami18n.test.ts +++ b/package/src/utils/__tests__/Streami18n.test.ts @@ -1,7 +1,7 @@ import { default as Dayjs } from 'dayjs'; import 'dayjs/locale/nl'; import localeData from 'dayjs/plugin/localeData'; -import moment from 'moment-timezone'; +import moment, { type Moment } from 'moment-timezone'; import frTranslations from '../../i18n/fr.json'; import nlTranslations from '../../i18n/nl.json'; @@ -39,7 +39,7 @@ describe('Streami18n instance - default', () => { it('should provide dayjs with default en locale', async () => { const { tDateTimeParser } = await streami18n.getTranslators(); expect(tDateTimeParser() instanceof Dayjs).toBe(true); - expect(tDateTimeParser().locale()).toBe('en'); + expect((tDateTimeParser() as Dayjs.Dayjs).locale()).toBe('en'); }); }); @@ -64,7 +64,7 @@ describe('Streami18n instance - with built-in language', () => { it('should provide dayjs with `nl` locale', async () => { const { tDateTimeParser } = await streami18n.getTranslators(); expect(tDateTimeParser() instanceof Dayjs).toBe(true); - expect(tDateTimeParser().locale()).toBe('nl'); + expect((tDateTimeParser() as Dayjs.Dayjs).locale()).toBe('nl'); }); }); @@ -93,7 +93,7 @@ describe('Streami18n instance - with built-in language', () => { it('should provide dayjs with default `en` locale', async () => { const { tDateTimeParser } = await streami18n.getTranslators(); expect(tDateTimeParser() instanceof Dayjs).toBe(true); - expect(tDateTimeParser().locale()).toBe('en'); + expect((tDateTimeParser() as Dayjs.Dayjs).locale()).toBe('en'); }); }); @@ -102,17 +102,21 @@ describe('Streami18n instance - with built-in language', () => { dayjsLocaleConfigForLanguage: customDayjsLocaleConfig, language: 'nl', }; - const streami18n = new Streami18n(streami18nOptions); + const streami18n = new Streami18n(streami18nOptions as unknown as ConstructorParameters[0]); it('should provide dayjs with given custom locale config', async () => { const { tDateTimeParser } = await streami18n.getTranslators(); expect(tDateTimeParser() instanceof Dayjs).toBe(true); - const localeConfig = tDateTimeParser().localeData(); + const localeConfig = (tDateTimeParser() as Dayjs.Dayjs).localeData() as unknown as Record; for (const key in streami18nOptions.dayjsLocaleConfigForLanguage) { if (typeof localeConfig[key] === 'function') { - expect(localeConfig[key]()).toStrictEqual(customDayjsLocaleConfig[key]); + expect((localeConfig[key] as () => unknown)()).toStrictEqual( + customDayjsLocaleConfig[key as keyof typeof customDayjsLocaleConfig], + ); } else { - expect(localeConfig[key]).toStrictEqual(customDayjsLocaleConfig[key]); + expect(localeConfig[key]).toStrictEqual( + customDayjsLocaleConfig[key as keyof typeof customDayjsLocaleConfig], + ); } } }); @@ -133,7 +137,9 @@ describe('Streami18n instance - with custom translations', () => { language: 'zh', translationsForLanguage: translations, }; - const streami18n = new Streami18n(streami18nOptions); + const streami18n = new Streami18n( + streami18nOptions as unknown as ConstructorParameters[0], + ); it('should provide given (chinese in this case) translator', async () => { const { t: _t } = await streami18n.getTranslators(); @@ -146,7 +152,7 @@ describe('Streami18n instance - with custom translations', () => { it('should provide dayjs with default `en` locale', async () => { const { tDateTimeParser } = await streami18n.getTranslators(); expect(tDateTimeParser() instanceof Dayjs).toBe(true); - expect(tDateTimeParser().locale()).toBe('en'); + expect((tDateTimeParser() as Dayjs.Dayjs).locale()).toBe('en'); }); }); }); @@ -162,7 +168,11 @@ describe('registerTranslation - register new language `mr` (Marathi)', () => { text1: 'अनुवादित मजकूर 1', text2: 'अनुवादित मजकूर 2', }; - streami18n.registerTranslation(languageCode, translations, customDayjsLocaleConfig); + streami18n.registerTranslation( + languageCode, + translations as unknown as Parameters[1], + customDayjsLocaleConfig as unknown as Parameters[2], + ); streami18n.setLanguage('mr'); @@ -176,12 +186,19 @@ describe('registerTranslation - register new language `mr` (Marathi)', () => { const { tDateTimeParser } = await streami18n.getTranslators(); expect(tDateTimeParser() instanceof Dayjs).toBe(true); - const localeConfig = tDateTimeParser().localeData(); + const localeConfig = (tDateTimeParser() as Dayjs.Dayjs).localeData() as unknown as Record< + string, + unknown + >; for (const key in customDayjsLocaleConfig) { if (typeof localeConfig[key] === 'function') { - expect(localeConfig[key]()).toStrictEqual(customDayjsLocaleConfig[key]); + expect((localeConfig[key] as () => unknown)()).toStrictEqual( + customDayjsLocaleConfig[key as keyof typeof customDayjsLocaleConfig], + ); } else { - expect(localeConfig[key]).toStrictEqual(customDayjsLocaleConfig[key]); + expect(localeConfig[key]).toStrictEqual( + customDayjsLocaleConfig[key as keyof typeof customDayjsLocaleConfig], + ); } } }); @@ -215,27 +232,34 @@ describe('Streami18n timezone', () => { it('is by default the local timezone', () => { const streamI18n = new Streami18n({ DateTimeParser: module }); const date = new Date(); - expect(streamI18n.tDateTimeParser(date).format('H')).toBe(date.getHours().toString()); + expect((streamI18n.tDateTimeParser(date) as Moment).format('H')).toBe( + date.getHours().toString(), + ); }); it('can be set to different timezone on init', () => { const streamI18n = new Streami18n({ DateTimeParser: module, timezone: 'Europe/Prague' }); const date = new Date(); - expect(streamI18n.tDateTimeParser(date).format('H')).not.toBe(date.getHours().toString()); - expect(streamI18n.tDateTimeParser(date).format('H')).not.toBe( + expect((streamI18n.tDateTimeParser(date) as Moment).format('H')).not.toBe( + date.getHours().toString(), + ); + expect((streamI18n.tDateTimeParser(date) as Moment).format('H')).not.toBe( (date.getUTCHours() - 2).toString(), ); }); it('is ignored if datetime parser does not support timezones', () => { - const tz = module.tz; - delete module.tz; + const mutableModule = module as unknown as { tz: unknown }; + const tz = mutableModule.tz; + delete (mutableModule as { tz?: unknown }).tz; const streamI18n = new Streami18n({ DateTimeParser: module, timezone: 'Europe/Prague' }); const date = new Date(); - expect(streamI18n.tDateTimeParser(date).format('H')).toBe(date.getHours().toString()); + expect((streamI18n.tDateTimeParser(date) as Moment).format('H')).toBe( + date.getHours().toString(), + ); - module.tz = tz; + mutableModule.tz = tz; }); describe('formatters property', () => { it('contains the default timestampFormatter', () => { @@ -244,17 +268,17 @@ describe('Streami18n timezone', () => { it('allows to override the default timestampFormatter', async () => { const i18n = new Streami18n({ formatters: { timestampFormatter: () => () => 'custom' }, - translationsForLanguage: { abc: '{{ value | timestampFormatter }}' }, + translationsForLanguage: { abc: '{{ value | timestampFormatter }}' } as unknown as Record, }); - await i18n.init(); + await (i18n as unknown as { init: () => Promise }).init(); expect(i18n.t('abc')).toBe('custom'); }); it('allows to add new custom formatter', async () => { const i18n = new Streami18n({ formatters: { customFormatter: () => () => 'custom' }, - translationsForLanguage: { abc: '{{ value | customFormatter }}' }, + translationsForLanguage: { abc: '{{ value | customFormatter }}' } as unknown as Record, }); - await i18n.init(); + await (i18n as unknown as { init: () => Promise }).init(); expect(i18n.t('abc')).toBe('custom'); }); }); From f9645239a36012126d34e01e3ae8dbf7bd9f8d83 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 13:20:27 +0200 Subject: [PATCH 05/15] refactor(tests): annotate high-traffic component tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Type annotations for 21 component test files. No behavior changes — only type imports, describe-scope `let` declarations, context-value casts via the established `as unknown as ContextValue` pattern, and narrow casts at SDK-type access points. - Channel/__tests__/{Channel,ownCapabilities,useMessageListPagination, isAttachmentEqualHandler}.test.tsx - ChannelList/__tests__/{ChannelList,ChannelListView}.test.tsx - ChannelPreview/__tests__/ChannelPreviewView.test.tsx - Chat/__tests__/Chat.test.tsx - Message/MessageItemView/__tests__/{Message,MessageAuthor, MessageContent,MessageItemView,MessagePinnedHeader,MessageReplies, MessageStatus,MessageTextContainer,ReactionListBottom, ReactionListTop}.test.tsx - MessageList/__tests__/{MessageList,ScrollToBottomButton}.test.tsx - Thread/__tests__/Thread.test.tsx Conventions applied: - `let client: StreamChat; let channel: Channel;` at describe scope. - `{...} as unknown as ChannelContextValue` (or `ChatContextValue`, `MessagesContextValue`, etc.) for partial context mocks — matches `components/MessageList/__tests__/useMessageList.test.tsx:35,43`. - `MessageResponse` → `LocalMessage` via small local `toLocalMessage` helper where needed; SDK's `formatMessage` isn't used because it requires a configured Channel. - `ComponentProps` for `renderComponent({ props })` args. - Numeric `id`s in mock-builder calls fixed to string. Pre-existing latent test bug noted: `MessageStatus.test.tsx` used `it.each('string', fn)` (malformed — string iterated as chars). Converted to `it.skip` to preserve pre-migration runtime behavior (the test body never executed) and filed as future work to fix the test properly. Takes test:typecheck 437 → 227 (−210). Full test suite: same pre-existing SQLite-isolation flake in offline-support; no regressions. --- .../Channel/__tests__/Channel.test.tsx | 234 +++++++++++------- .../isAttachmentEqualHandler.test.tsx | 35 ++- .../__tests__/ownCapabilities.test.tsx | 28 ++- .../useMessageListPagination.test.tsx | 191 ++++++++------ .../__tests__/ChannelList.test.tsx | 100 +++++--- .../__tests__/ChannelListView.test.tsx | 50 ++-- .../__tests__/ChannelPreviewView.test.tsx | 49 ++-- .../components/Chat/__tests__/Chat.test.tsx | 31 +-- .../__tests__/Message.test.tsx | 16 +- .../__tests__/MessageAuthor.test.tsx | 35 ++- .../__tests__/MessageContent.test.tsx | 49 +++- .../__tests__/MessageItemView.test.tsx | 2 +- .../__tests__/MessagePinnedHeader.test.tsx | 15 +- .../__tests__/MessageReplies.test.tsx | 64 +++-- .../__tests__/MessageStatus.test.tsx | 36 ++- .../__tests__/MessageTextContainer.test.tsx | 8 +- .../__tests__/ReactionListBottom.test.tsx | 14 +- .../__tests__/ReactionListTop.test.tsx | 2 +- .../__tests__/MessageList.test.tsx | 64 ++--- .../__tests__/ScrollToBottomButton.test.tsx | 17 +- .../Thread/__tests__/Thread.test.tsx | 63 +++-- 21 files changed, 700 insertions(+), 403 deletions(-) diff --git a/package/src/components/Channel/__tests__/Channel.test.tsx b/package/src/components/Channel/__tests__/Channel.test.tsx index 80559623f5..9af8d839cb 100644 --- a/package/src/components/Channel/__tests__/Channel.test.tsx +++ b/package/src/components/Channel/__tests__/Channel.test.tsx @@ -2,15 +2,19 @@ import React, { useContext, useEffect } from 'react'; import { View } from 'react-native'; import { act, cleanup, render, renderHook, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat as StreamChatType } from 'stream-chat'; import { StreamChat } from 'stream-chat'; +import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext'; import { ChannelContext, ChannelProvider } from '../../../contexts/channelContext/ChannelContext'; import { ChannelsStateProvider } from '../../../contexts/channelsStateContext/ChannelsStateContext'; +import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext'; import { MessagesContext, MessagesProvider, } from '../../../contexts/messagesContext/MessagesContext'; +import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; import { ThreadContext, ThreadProvider } from '../../../contexts/threadContext/ThreadContext'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; @@ -34,7 +38,13 @@ import * as MessageListPaginationHooks from '../hooks/useMessageListPagination'; // This component is used for performing effects in a component that consumes ChannelContext, // i.e. making use of the callbacks & values provided by the Channel component. // the effect is called every time channelContext changes -const CallbackEffectWithContext = ({ callback, context }) => { +const CallbackEffectWithContext = ({ + callback, + context, +}: { + callback: (ctx: unknown) => void; + context: React.Context; +}) => { const ctx = useContext(context); useEffect(() => { callback(ctx); @@ -43,7 +53,13 @@ const CallbackEffectWithContext = ({ callback, context }) => { return ; }; -const ContextConsumer = ({ context, fn }) => { +const ContextConsumer = ({ + context, + fn, +}: { + context: React.Context; + fn: (ctx: unknown) => void; +}) => { fn(useContext(context)); return ; }; @@ -51,17 +67,23 @@ const ContextConsumer = ({ context, fn }) => { const channelType = 'messaging'; const channelId = 'test-channel'; const channelCid = `${channelType}:${channelId}`; -let chatClient; -let channel; +let chatClient: StreamChatType; +let channel: ChannelType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ cid: channelCid, user })]; -const renderComponent = (props = {}, callback = () => {}, context = ChannelContext) => +type RenderComponentProps = Record & { children?: React.ReactNode }; + +const renderComponent = ( + props: RenderComponentProps = {}, + callback: (ctx: unknown) => void = () => {}, + context: React.Context = ChannelContext as unknown as React.Context, +) => render( - + )}> {props.children} @@ -73,7 +95,7 @@ describe('Channel', () => { beforeEach(async () => { const members = [generateMember({ user })]; const mockedChannel = generateChannelResponse({ - cid: channelCid, + channel: { cid: channelCid }, id: channelId, members, messages, @@ -81,8 +103,8 @@ describe('Channel', () => { }); chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); - channel.cid = mockedChannel.channel.cid; + channel = chatClient.channel('messaging', mockedChannel.channel.id); + channel.cid = mockedChannel.channel.cid as string; const getConfigSpy = jest.fn(); channel.getConfig = getConfigSpy; }); @@ -158,14 +180,18 @@ describe('Channel', () => { // and then calls hasThread with the thread id if it was set. const { rerender } = renderComponent( { channel }, - ({ openThread, thread }) => { + (ctx) => { + const { openThread, thread } = ctx as { + openThread: (m: unknown) => void; + thread: { id: string } | null; + }; if (!thread) { openThread(threadMessage); } else { hasThread(thread.id); } }, - ThreadContext, + ThreadContext as unknown as React.Context, ); rerender( @@ -173,14 +199,18 @@ describe('Channel', () => { { + callback={(ctx) => { + const { openThread, thread } = ctx as { + openThread: (m: unknown) => void; + thread: { id: string } | null; + }; if (!thread) { openThread(threadMessage); } else { hasThread(thread.id); } }} - context={ThreadContext} + context={ThreadContext as unknown as React.Context} /> @@ -189,7 +219,9 @@ describe('Channel', () => { await waitFor(() => expect(hasThread).toHaveBeenCalledWith(threadMessage.id)); }); - const queryChannelWithNewMessages = (newMessages) => + const queryChannelWithNewMessages = ( + newMessages: ReturnType[], + ) => // generate new channel mock from existing channel with new messages added getOrCreateChannelApi( generateChannelResponse({ @@ -197,7 +229,9 @@ describe('Channel', () => { config: channel.getConfig(), id: channel.id, type: channel.type, - }, + } as unknown as NonNullable< + Parameters[0] + >['channel'], messages: newMessages, }), ); @@ -212,7 +246,7 @@ describe('Channel', () => { () => { useMockedApis(chatClient, [queryChannelWithNewMessages(newMessages)]); }, - MessagesContext, + MessagesContext as unknown as React.Context, ); await waitFor(() => expect(channelQuerySpy).toHaveBeenCalled()); @@ -221,7 +255,7 @@ describe('Channel', () => { describe('ChannelContext', () => { it('renders children without crashing', async () => { const { getByTestId } = render( - + , ); @@ -230,7 +264,7 @@ describe('Channel', () => { }); it('exposes the channel context', async () => { - let context; + let context: ChannelContextValue | undefined; const mockContext = { channel, @@ -240,11 +274,11 @@ describe('Channel', () => { }; render( - + } fn={(ctx) => { - context = ctx; + context = ctx as ChannelContextValue; }} /> , @@ -252,10 +286,11 @@ describe('Channel', () => { await waitFor(() => { expect(context).toBeInstanceOf(Object); - expect(context.channel).toBeInstanceOf(Object); - expect(context.client).toBeInstanceOf(StreamChat); - expect(context.markRead).toBeInstanceOf(Function); - expect(context.watcherCount).toBe(5); + const ctx = context as unknown as Record; + expect(ctx.channel).toBeInstanceOf(Object); + expect(ctx.client).toBeInstanceOf(StreamChat); + expect(ctx.markRead).toBeInstanceOf(Function); + expect(ctx.watcherCount).toBe(5); }); }); }); @@ -263,7 +298,7 @@ describe('Channel', () => { describe('MessagesContext', () => { it('renders children without crashing', async () => { const { getByTestId } = render( - + , ); @@ -272,7 +307,7 @@ describe('Channel', () => { }); it('exposes the messages context', async () => { - let context; + let context: MessagesContextValue | undefined; const mockContext = { Attachment, @@ -282,11 +317,11 @@ describe('Channel', () => { }; render( - + } fn={(ctx) => { - context = ctx; + context = ctx as MessagesContextValue; }} /> , @@ -294,10 +329,11 @@ describe('Channel', () => { await waitFor(() => { expect(context).toBeInstanceOf(Object); - expect(context.Attachment).toBeInstanceOf(Function); - expect(context.editing).toBe(false); - expect(context.messages).toBeInstanceOf(Array); - expect(context.sendMessage).toBeInstanceOf(Function); + const ctx = context as unknown as Record; + expect(ctx.Attachment).toBeInstanceOf(Function); + expect(ctx.editing).toBe(false); + expect(ctx.messages).toBeInstanceOf(Array); + expect(ctx.sendMessage).toBeInstanceOf(Function); }); }); }); @@ -305,7 +341,7 @@ describe('Channel', () => { describe('ThreadContext', () => { it('renders children without crashing', async () => { const { getByTestId } = render( - + , ); @@ -314,7 +350,7 @@ describe('Channel', () => { }); it('exposes the thread context', async () => { - let context; + let context: ThreadContextValue | undefined; const mockContext = { openThread: () => {}, @@ -324,11 +360,11 @@ describe('Channel', () => { }; render( - + } fn={(ctx) => { - context = ctx; + context = ctx as ThreadContextValue; }} /> , @@ -336,22 +372,24 @@ describe('Channel', () => { await waitFor(() => { expect(context).toBeInstanceOf(Object); - expect(context.openThread).toBeInstanceOf(Function); - expect(context.thread).toBeInstanceOf(Object); - expect(context.threadHasMore).toBe(true); - expect(context.threadLoadingMore).toBe(false); + expect(context!.openThread).toBeInstanceOf(Function); + expect(context!.thread).toBeInstanceOf(Object); + expect(context!.threadHasMore).toBe(true); + expect(context!.threadLoadingMore).toBe(false); }); }); }); }); describe('Channel initial load useEffect', () => { - let chatClient; + let chatClient: StreamChatType; - const renderComponent = (props = {}) => + const renderComponent = (props: RenderComponentProps = {}) => render( - {props.children} + )}> + {props.children} + , ); @@ -365,13 +403,13 @@ describe('Channel initial load useEffect', () => { }); it('should still call channel.watch if we are online and DB channels are loaded', async () => { - const messages = Array.from({ length: 10 }, (_, i) => generateMessage({ id: i })); + const messages = Array.from({ length: 10 }, (_, i) => generateMessage({ id: String(i) })); const mockedChannel = generateChannelResponse({ messages, }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); channel.offlineMode = true; channel.state = { @@ -379,7 +417,7 @@ describe('Channel initial load useEffect', () => { messagePagination: { hasPrev: true, }, - }; + } as unknown as typeof channel.state; const watchSpy = jest.fn(); channel.watch = watchSpy; @@ -389,29 +427,34 @@ describe('Channel initial load useEffect', () => { }); it("should call channel.watch if channel is initialized and it's not in offline mode", async () => { - const messages = Array.from({ length: 10 }, (_, i) => generateMessage({ id: i })); + const messages = Array.from({ length: 10 }, (_, i) => generateMessage({ id: String(i) })); const mockedChannel = generateChannelResponse({ messages, }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); channel.state = { ...channelInitialState, members: Object.fromEntries( - Array.from({ length: 10 }, (_, i) => [i, generateMember({ id: i })]), + Array.from({ length: 10 }, (_, i) => [ + i, + generateMember({ user_id: String(i) } as unknown as Partial< + Parameters[0] + >), + ]), ), messagePagination: { hasPrev: true, }, - messages: Array.from({ length: 10 }, (_, i) => generateMessage({ id: i })), - }; + messages: Array.from({ length: 10 }, (_, i) => generateMessage({ id: String(i) })), + } as unknown as typeof channel.state; const watchSpy = jest.fn(); channel.offlineMode = false; - channel.initialied = false; + (channel as unknown as { initialied: boolean }).initialied = false; channel.watch = watchSpy; renderComponent({ channel }); @@ -420,11 +463,15 @@ describe('Channel initial load useEffect', () => { const { result: channelState } = renderHook(() => useChannelDataState(channel)); await waitFor(() => expect(watchSpy).toHaveBeenCalled()); - await waitFor(() => expect(channelMessageState.current.state.messages).toHaveLength(10)); - await waitFor(() => expect(Object.keys(channelState.current.state.members)).toHaveLength(10)); + await waitFor(() => expect(channelMessageState.current.state.messages!).toHaveLength(10)); + await waitFor(() => expect(Object.keys(channelState.current.state.members!)).toHaveLength(10)); }); - function getElementsAround(array, key, id) { + function getElementsAround>( + array: T[], + key: keyof T, + id: unknown, + ) { const index = array.findIndex((obj) => obj[key] === id); if (index === -1) { @@ -437,19 +484,23 @@ describe('Channel initial load useEffect', () => { } it('should call the loadChannelAroundMessage when messageId is passed to a channel', async () => { - const messages = Array.from({ length: 105 }, (_, i) => generateMessage({ id: i })); + const messages = Array.from({ length: 105 }, (_, i) => generateMessage({ id: String(i) })); const messageToSearch = messages[50]; const mockedChannel = generateChannelResponse({ messages, }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const loadMessageIntoState = jest.fn(() => { - const newMessages = getElementsAround(messages, 'id', messageToSearch.id); - channel.state.messages = newMessages; + const newMessages = getElementsAround( + messages as unknown as Record[], + 'id', + messageToSearch.id, + ); + channel.state.messages = newMessages as unknown as typeof channel.state.messages; }); channel.state = { @@ -460,7 +511,7 @@ describe('Channel initial load useEffect', () => { hasPrev: true, }, messages, - }; + } as unknown as typeof channel.state; renderComponent({ channel, messageId: messageToSearch.id }); @@ -469,10 +520,10 @@ describe('Channel initial load useEffect', () => { }); const { result: channelMessageState } = renderHook(() => useChannelMessageDataState(channel)); - await waitFor(() => expect(channelMessageState.current.state.messages).toHaveLength(25)); + await waitFor(() => expect(channelMessageState.current.state.messages!).toHaveLength(25)); await waitFor(() => expect( - channelMessageState.current.state.messages.find( + channelMessageState.current.state.messages!.find( (message) => message.id === messageToSearch.id, ), ).toBeTruthy(), @@ -487,30 +538,33 @@ describe('Channel initial load useEffect', () => { jest.restoreAllMocks(); cleanup(); }); - const mockedHook = (values) => - jest.spyOn(MessageListPaginationHooks, 'useMessageListPagination').mockImplementation(() => ({ - copyMessagesStateFromChannel: jest.fn(), - loadChannelAroundMessage: jest.fn(), - loadChannelAtFirstUnreadMessage: jest.fn(), - loadInitialMessagesStateFromChannel: jest.fn(), - loadLatestMessages: jest.fn(), - loadMore: jest.fn(), - loadMoreRecent: jest.fn(), - state: { ...channelInitialState }, - ...values, - })); + const mockedHook = (values: Record) => + jest.spyOn(MessageListPaginationHooks, 'useMessageListPagination').mockImplementation( + () => + ({ + copyMessagesStateFromChannel: jest.fn(), + loadChannelAroundMessage: jest.fn(), + loadChannelAtFirstUnreadMessage: jest.fn(), + loadInitialMessagesStateFromChannel: jest.fn(), + loadLatestMessages: jest.fn(), + loadMore: jest.fn(), + loadMoreRecent: jest.fn(), + state: { ...channelInitialState }, + ...values, + }) as unknown as ReturnType, + ); it("should not call loadChannelAtFirstUnreadMessage if channel's unread count is 0", async () => { const mockedChannel = generateChannelResponse({ messages: Array.from({ length: 10 }, (_, i) => generateMessage({ text: `message-${i}` })), }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const user = generateUser(); - const read_data = {}; + const read_data: Record = {}; - read_data[chatClient.user.id] = { + read_data[chatClient.user!.id] = { last_read: new Date(), user, }; @@ -518,7 +572,7 @@ describe('Channel initial load useEffect', () => { channel.state = { ...channelInitialState, read: read_data, - }; + } as unknown as typeof channel.state; jest.spyOn(channel, 'countUnread').mockImplementation(() => 0); const loadChannelAtFirstUnreadMessageFn = jest.fn(); @@ -538,14 +592,14 @@ describe('Channel initial load useEffect', () => { }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const user = generateUser(); const numberOfUnreadMessages = 15; - const read_data = {}; + const read_data: Record = {}; - read_data[chatClient.user.id] = { + read_data[chatClient.user!.id] = { last_read: new Date(), unread_messages: numberOfUnreadMessages, user, @@ -553,7 +607,7 @@ describe('Channel initial load useEffect', () => { channel.state = { ...channelInitialState, read: read_data, - }; + } as unknown as typeof channel.state; jest.spyOn(channel, 'countUnread').mockImplementation(() => numberOfUnreadMessages); const loadChannelAtFirstUnreadMessageFn = jest.fn(); @@ -573,14 +627,14 @@ describe('Channel initial load useEffect', () => { }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const user = generateUser(); const numberOfUnreadMessages = 2; - const read_data = {}; + const read_data: Record = {}; - read_data[chatClient.user.id] = { + read_data[chatClient.user!.id] = { last_read: new Date(), unread_messages: numberOfUnreadMessages, user, @@ -588,7 +642,7 @@ describe('Channel initial load useEffect', () => { channel.state = { ...channelInitialState, read: read_data, - }; + } as unknown as typeof channel.state; jest.spyOn(channel, 'countUnread').mockImplementation(() => numberOfUnreadMessages); const loadChannelAtFirstUnreadMessageFn = jest.fn(); @@ -609,7 +663,7 @@ describe('Channel initial load useEffect', () => { }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); renderComponent({ channel }); diff --git a/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx b/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx index 7c02654712..78a11141f1 100644 --- a/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx +++ b/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Text } from 'react-native'; import { act, cleanup, render, waitFor } from '@testing-library/react-native'; +import type { Attachment, Channel as ChannelType, StreamChat } from 'stream-chat'; import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; @@ -19,14 +20,16 @@ import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { MessageList } from '../../MessageList/MessageList'; +type AttachmentWithCustomField = Attachment & { customField?: string }; + describe('isAttachmentEqualHandler', () => { - let channel; - let chatClient; + let channel: ChannelType; + let chatClient: StreamChat; const user = generateUser({ id: 'id', name: 'name' }); const messages = [ generateMessage({ - attachments: [{ customField: 'custom-field', type: 'test' }], + attachments: [{ customField: 'custom-field', type: 'test' } as AttachmentWithCustomField], user, }), ]; @@ -40,7 +43,7 @@ describe('isAttachmentEqualHandler', () => { chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); }); @@ -50,7 +53,10 @@ describe('isAttachmentEqualHandler', () => { }); const getMessageWithCustomFields = () => { - const isAttachmentEqualHandler = (prevProps, nextProps) => { + const isAttachmentEqualHandler = ( + prevProps: AttachmentWithCustomField, + nextProps: AttachmentWithCustomField, + ) => { const propsEqual = prevProps.customField === nextProps.customField && prevProps.type === nextProps.type; if (!propsEqual) { @@ -64,14 +70,23 @@ describe('isAttachmentEqualHandler', () => { { + UnsupportedAttachment: ({ attachment }) => { + const { customField, type } = attachment as AttachmentWithCustomField; if (type === 'test') { return {customField}; } + return null; }, }} > - + ['isAttachmentEqual'] + } + > @@ -92,8 +107,10 @@ describe('isAttachmentEqualHandler', () => { chatClient, { ...messages[0], - attachments: [{ customField: 'custom-field-2', type: 'test' }], - updated_at: new Date(), + attachments: [ + { customField: 'custom-field-2', type: 'test' } as AttachmentWithCustomField, + ], + updated_at: new Date() as unknown as string, }, channel, ); diff --git a/package/src/components/Channel/__tests__/ownCapabilities.test.tsx b/package/src/components/Channel/__tests__/ownCapabilities.test.tsx index 6b6af3705d..b81ebf271f 100644 --- a/package/src/components/Channel/__tests__/ownCapabilities.test.tsx +++ b/package/src/components/Channel/__tests__/ownCapabilities.test.tsx @@ -4,6 +4,7 @@ import { FlatList } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { act, fireEvent, render, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, MessageResponse, StreamChat } from 'stream-chat'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; import { allOwnCapabilities } from '../../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext'; @@ -31,10 +32,10 @@ describe('Own capabilities', () => { user: otherUser, }); - let chatClient; - let channel; + let chatClient: StreamChat; + let channel: ChannelType; - const initializeChannel = async (c) => { + const initializeChannel = async (c: ReturnType) => { useMockedApis(chatClient, [getOrCreateChannelApi(c)]); channel = chatClient.channel('messaging'); @@ -48,7 +49,7 @@ describe('Own capabilities', () => { }); }); - const getComponent = (props = {}) => ( + const getComponent = (props: Partial> = {}) => ( @@ -61,7 +62,7 @@ describe('Own capabilities', () => { ); - const generateChannelWithCapabilities = async (capabilities = []) => { + const generateChannelWithCapabilities = async (capabilities: string[] = []) => { const c = generateChannelResponse({ channel: { own_capabilities: capabilities, @@ -71,12 +72,15 @@ describe('Own capabilities', () => { await initializeChannel(c); }; - const renderChannelAndOpenMessageActionsList = async (targetMessage, props = {}) => { + const renderChannelAndOpenMessageActionsList = async ( + targetMessage: MessageResponse, + props: Partial> = {}, + ) => { const { findByTestId, queryByLabelText, queryByText, unmount } = render(getComponent(props)); - await waitFor(() => queryByText(targetMessage.text)); + await waitFor(() => queryByText(targetMessage.text as string)); act(() => { - fireEvent(queryByText(targetMessage.text), 'onLongPress'); + fireEvent(queryByText(targetMessage.text as string)!, 'onLongPress'); }); await waitFor(() => expect(!!queryByLabelText('Message action list')).toBeTruthy()); @@ -363,7 +367,7 @@ describe('Own capabilities', () => { const sendMessage = jest.fn(); channel.sendMessage = sendMessage; act(() => { - fireEvent(queryByTestId('send-button'), 'onPress'); + fireEvent(queryByTestId('send-button')!, 'onPress'); }); await waitFor(() => expect(sendMessage).toHaveBeenCalledTimes(0)); @@ -378,10 +382,10 @@ describe('Own capabilities', () => { const mockFn = jest.fn(); const { queryByTestId } = render( getComponent({ - doSendMessageRequest: () => { + doSendMessageRequest: (() => { mockFn(); return sendMessageApi(); - }, + }) as unknown as React.ComponentProps['doSendMessageRequest'], }), ); @@ -397,7 +401,7 @@ describe('Own capabilities', () => { }); act(() => { - fireEvent(queryByTestId('send-button'), 'onPress'); + fireEvent(queryByTestId('send-button')!, 'onPress'); }); await waitFor(() => expect(mockFn).toHaveBeenCalledTimes(1)); diff --git a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx index eed226f56b..646684352f 100644 --- a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx +++ b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx @@ -1,4 +1,5 @@ import { act, cleanup, renderHook, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, MessageResponse, StreamChat } from 'stream-chat'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; import { useMockedApis } from '../../../mock-builders/api/useMockedApis'; @@ -11,23 +12,26 @@ import * as ChannelStateHooks from '../hooks/useChannelDataState'; import { useMessageListPagination } from '../hooks/useMessageListPagination'; describe('useMessageListPagination', () => { - let chatClient; - let channel; - - const mockedHook = (state, values) => - jest.spyOn(ChannelStateHooks, 'useChannelMessageDataState').mockImplementation(() => ({ - copyMessagesStateFromChannel: jest.fn(), - jumpToLatestMessage: jest.fn(), - jumpToMessageFinished: jest.fn(), - loadInitialMessagesStateFromChannel: jest.fn(), - loadMoreFinished: jest.fn(), - loadMoreRecentFinished: jest.fn(), - setLoading: jest.fn(), - setLoadingMore: jest.fn(), - setLoadingMoreRecent: jest.fn(), - state: { ...channelInitialState, ...state }, - ...values, - })); + let chatClient: StreamChat; + let channel: ChannelType; + + const mockedHook = (state: Record, values?: Record) => + jest.spyOn(ChannelStateHooks, 'useChannelMessageDataState').mockImplementation( + () => + ({ + copyMessagesStateFromChannel: jest.fn(), + jumpToLatestMessage: jest.fn(), + jumpToMessageFinished: jest.fn(), + loadInitialMessagesStateFromChannel: jest.fn(), + loadMoreFinished: jest.fn(), + loadMoreRecentFinished: jest.fn(), + setLoading: jest.fn(), + setLoadingMore: jest.fn(), + setLoadingMoreRecent: jest.fn(), + state: { ...channelInitialState, ...state }, + ...values, + }) as unknown as ReturnType, + ); beforeEach(async () => { // Reset all modules before each test @@ -40,7 +44,7 @@ describe('useMessageListPagination', () => { }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); }); @@ -56,8 +60,8 @@ describe('useMessageListPagination', () => { const loadMessageIntoState = jest.fn(() => { channel.state.messages = Array.from({ length: 20 }, (_, i) => generateMessage({ text: `message-${i}` }), - ); - channel.state.messagePagination.hasPrev = true; + ) as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { ...channelInitialState, @@ -66,7 +70,7 @@ describe('useMessageListPagination', () => { hasNext: true, hasPrev: true, }, - }; + } as unknown as typeof channel.state; const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { @@ -76,7 +80,7 @@ describe('useMessageListPagination', () => { await waitFor(() => { expect(loadMessageIntoState).toHaveBeenCalledTimes(1); expect(result.current.state.hasMore).toBe(true); - expect(result.current.state.messages.length).toBe(20); + expect(result.current.state.messages!.length).toBe(20); }); }); @@ -96,8 +100,8 @@ describe('useMessageListPagination', () => { hasNext: true, hasPrev: false, }, - }; - channel.query = queryFn; + } as unknown as typeof channel.state; + channel.query = queryFn as unknown as typeof channel.query; const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { @@ -117,8 +121,8 @@ describe('useMessageListPagination', () => { hasNext: true, hasPrev: true, }, - }; - channel.query = queryFn; + } as unknown as typeof channel.state; + channel.query = queryFn as unknown as typeof channel.query; mockedHook({ loadingMore: true, loadingMoreRecent: true }); @@ -140,8 +144,8 @@ describe('useMessageListPagination', () => { const queryFn = jest.fn(() => { channel.state.messages = Array.from({ length: 40 }, (_, i) => generateMessage({ text: `message-${i}` }), - ); - channel.state.messagePagination.hasPrev = true; + ) as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { ...channelInitialState, @@ -150,8 +154,8 @@ describe('useMessageListPagination', () => { hasPrev: true, }, messages, - }; - channel.query = queryFn; + } as unknown as typeof channel.state; + channel.query = queryFn as unknown as typeof channel.query; const { result } = renderHook(() => useMessageListPagination({ channel })); @@ -167,7 +171,7 @@ describe('useMessageListPagination', () => { }, }); expect(result.current.state.hasMore).toBe(true); - expect(result.current.state.messages.length).toBe(40); + expect(result.current.state.messages!.length).toBe(40); }); }); }); @@ -189,8 +193,8 @@ describe('useMessageListPagination', () => { hasNext: false, hasPrev: true, }, - }; - channel.query = queryFn; + } as unknown as typeof channel.state; + channel.query = queryFn as unknown as typeof channel.query; const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { @@ -210,8 +214,8 @@ describe('useMessageListPagination', () => { hasNext: true, hasPrev: true, }, - }; - channel.query = queryFn; + } as unknown as typeof channel.state; + channel.query = queryFn as unknown as typeof channel.query; mockedHook({ loadingMore: true, loadingMoreRecent: true }); @@ -233,8 +237,8 @@ describe('useMessageListPagination', () => { const queryFn = jest.fn(() => { channel.state.messages = Array.from({ length: 40 }, (_, i) => generateMessage({ text: `message-${i}` }), - ); - channel.state.messagePagination.hasPrev = true; + ) as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { ...channelInitialState, @@ -243,8 +247,8 @@ describe('useMessageListPagination', () => { hasPrev: true, }, messages, - }; - channel.query = queryFn; + } as unknown as typeof channel.state; + channel.query = queryFn as unknown as typeof channel.query; const { result } = renderHook(() => useMessageListPagination({ channel })); @@ -258,7 +262,7 @@ describe('useMessageListPagination', () => { watchers: { limit: 10 }, }); expect(result.current.state.hasMore).toBe(true); - expect(result.current.state.messages.length).toBe(40); + expect(result.current.state.messages!.length).toBe(40); }); }); }); @@ -276,8 +280,8 @@ describe('useMessageListPagination', () => { const loadMessageIntoState = jest.fn(() => { channel.state.messages = Array.from({ length: 20 }, (_, i) => generateMessage({ text: `message-${i}` }), - ); - channel.state.messagePagination.hasPrev = true; + ) as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { ...channelInitialState, @@ -286,7 +290,7 @@ describe('useMessageListPagination', () => { hasNext: true, hasPrev: true, }, - }; + } as unknown as typeof channel.state; const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { @@ -302,8 +306,8 @@ describe('useMessageListPagination', () => { const loadMessageIntoState = jest.fn(() => { channel.state.messages = Array.from({ length: 20 }, (_, i) => generateMessage({ text: `message-${i}` }), - ); - channel.state.messagePagination.hasPrev = true; + ) as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { ...channelInitialState, @@ -312,7 +316,7 @@ describe('useMessageListPagination', () => { hasNext: false, hasPrev: true, }, - }; + } as unknown as typeof channel.state; const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { @@ -323,7 +327,7 @@ describe('useMessageListPagination', () => { expect(loadMessageIntoState).toHaveBeenCalledTimes(1); expect(result.current.state.hasMore).toBe(true); expect(result.current.state.hasMoreNewer).toBe(false); - expect(result.current.state.messages.length).toBe(20); + expect(result.current.state.messages!.length).toBe(20); expect(result.current.state.targetedMessageId).toBe('message-5'); }); }); @@ -343,8 +347,8 @@ describe('useMessageListPagination', () => { generateMessage({ text: `message-${i}` }), ); const loadMessageIntoState = jest.fn(() => { - channel.state.messages = messages; - channel.state.messagePagination.hasPrev = true; + channel.state.messages = messages as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { ...channelInitialState, @@ -353,7 +357,7 @@ describe('useMessageListPagination', () => { hasNext: true, hasPrev: true, }, - }; + } as unknown as typeof channel.state; const user = generateUser(); const channelUnreadState = { @@ -367,7 +371,11 @@ describe('useMessageListPagination', () => { const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { - await result.current.loadChannelAtFirstUnreadMessage({ channelUnreadState }); + await result.current.loadChannelAtFirstUnreadMessage({ + channelUnreadState: channelUnreadState as unknown as Parameters< + typeof result.current.loadChannelAtFirstUnreadMessage + >[0]['channelUnreadState'], + }); }); await waitFor(() => { @@ -376,10 +384,32 @@ describe('useMessageListPagination', () => { }); const generateMessageArray = (length = 20) => - Array.from({ length }, (_, i) => generateMessage({ id: i, text: `message-${i}` })); + Array.from({ length }, (_, i) => generateMessage({ id: String(i), text: `message-${i}` })); + + type TestCaseUnreadState = { + first_unread_message_id?: string; + last_read_message_id?: string; + unread_messages: number; + }; + + type TestCase = { + channelUnreadState: (messages: MessageResponse[]) => TestCaseUnreadState; + expectedCalls: { + jumpToMessageFinishedCalls: number; + loadMessageIntoStateCalls: number; + setChannelUnreadStateCalls: number; + setTargetedMessageIdCalls: number; + targetedMessageId: (messages: MessageResponse[]) => string; + }; + initialMessages: MessageResponse[]; + name: string; + setupLoadMessageIntoState: + | ((channel: ChannelType) => jest.Mock) + | null; + }; // Test cases with different scenarios - const testCases = [ + const testCases: TestCase[] = [ { channelUnreadState: (messages) => ({ first_unread_message_id: messages[2].id, @@ -398,7 +428,7 @@ describe('useMessageListPagination', () => { }, { channelUnreadState: () => ({ - first_unread_message_id: 21, + first_unread_message_id: '21', unread_messages: 2, }), expectedCalls: { @@ -406,19 +436,21 @@ describe('useMessageListPagination', () => { loadMessageIntoStateCalls: 1, setChannelUnreadStateCalls: 0, setTargetedMessageIdCalls: 1, - targetedMessageId: () => 21, + targetedMessageId: () => '21', }, initialMessages: generateMessageArray(), name: 'first_unread_message_id not present in current message set', setupLoadMessageIntoState: (channel) => { const loadMessageIntoState = jest.fn(() => { const newMessages = Array.from({ length: 20 }, (_, i) => - generateMessage({ id: i + 21, text: `message-${i + 21}` }), + generateMessage({ id: String(i + 21), text: `message-${i + 21}` }), ); - channel.state.messages = newMessages; - channel.state.messagePagination.hasPrev = true; + channel.state.messages = newMessages as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); - channel.state.loadMessageIntoState = loadMessageIntoState; + ( + channel.state as unknown as { loadMessageIntoState: jest.Mock } + ).loadMessageIntoState = loadMessageIntoState; return loadMessageIntoState; }, }, @@ -440,7 +472,7 @@ describe('useMessageListPagination', () => { }, { channelUnreadState: () => ({ - last_read_message_id: 21, + last_read_message_id: '21', unread_messages: 2, }), expectedCalls: { @@ -448,19 +480,21 @@ describe('useMessageListPagination', () => { loadMessageIntoStateCalls: 1, setChannelUnreadStateCalls: 1, setTargetedMessageIdCalls: 1, - targetedMessageId: () => 22, + targetedMessageId: () => '22', }, initialMessages: generateMessageArray(), name: 'last_read_message_id not present in current message set', setupLoadMessageIntoState: (channel) => { const loadMessageIntoState = jest.fn(() => { const newMessages = Array.from({ length: 20 }, (_, i) => - generateMessage({ id: i + 21, text: `message-${i + 21}` }), + generateMessage({ id: String(i + 21), text: `message-${i + 21}` }), ); - channel.state.messages = newMessages; - channel.state.messagePagination.hasPrev = true; + channel.state.messages = newMessages as unknown as typeof channel.state.messages; + (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); - channel.state.loadMessageIntoState = loadMessageIntoState; + ( + channel.state as unknown as { loadMessageIntoState: jest.Mock } + ).loadMessageIntoState = loadMessageIntoState; return loadMessageIntoState; }, }, @@ -476,7 +510,7 @@ describe('useMessageListPagination', () => { hasPrev: true, }, messages, - }; + } as unknown as typeof channel.state; // Setup additional mocks if needed const loadMessageIntoStateMock = testCase.setupLoadMessageIntoState @@ -502,7 +536,9 @@ describe('useMessageListPagination', () => { // Execute the method await act(async () => { await result.current.loadChannelAtFirstUnreadMessage({ - channelUnreadState, + channelUnreadState: channelUnreadState as unknown as Parameters< + typeof result.current.loadChannelAtFirstUnreadMessage + >[0]['channelUnreadState'], setChannelUnreadState: setChannelUnreadStateMock, setTargetedMessage: setTargetedMessageIdMock, }); @@ -537,8 +573,8 @@ describe('useMessageListPagination', () => { const messages = Array.from({ length: 20 }, (_, i) => generateMessage({ - created_at: new Date('2021-09-01T00:00:00.000Z'), - id: i, + created_at: new Date('2021-09-01T00:00:00.000Z') as unknown as string, + id: String(i), text: `message-${i}`, }), ); @@ -546,9 +582,9 @@ describe('useMessageListPagination', () => { const user = generateUser(); it.each` - scenario | last_read | expectedQueryCalls | expectedJumpToMessageFinishedCalls | expectedSetChannelUnreadStateCalls | expectedSetTargetedMessageCalls | expectedTargetedMessageId - ${'when last_read matches a message'} | ${new Date(messages[10].created_at)} | ${0} | ${1} | ${1} | ${1} | ${10} - ${'when last_read does not match any message'} | ${new Date('2021-09-02T00:00:00.000Z')} | ${1} | ${0} | ${0} | ${0} | ${undefined} + scenario | last_read | expectedQueryCalls | expectedJumpToMessageFinishedCalls | expectedSetChannelUnreadStateCalls | expectedSetTargetedMessageCalls | expectedTargetedMessageId + ${'when last_read matches a message'} | ${new Date(messages[10].created_at as unknown as Date)} | ${0} | ${1} | ${1} | ${1} | ${'10'} + ${'when last_read does not match any message'} | ${new Date('2021-09-02T00:00:00.000Z')} | ${1} | ${0} | ${0} | ${0} | ${undefined} `( '$scenario', async ({ @@ -558,6 +594,13 @@ describe('useMessageListPagination', () => { expectedSetTargetedMessageCalls, expectedTargetedMessageId, last_read, + }: { + expectedJumpToMessageFinishedCalls: number; + expectedQueryCalls: number; + expectedSetChannelUnreadStateCalls: number; + expectedSetTargetedMessageCalls: number; + expectedTargetedMessageId: string | undefined; + last_read: Date; }) => { // Set up channel state channel.state = { @@ -567,7 +610,7 @@ describe('useMessageListPagination', () => { hasPrev: true, }, messages, - }; + } as unknown as typeof channel.state; const channelUnreadState = { last_read, @@ -577,7 +620,7 @@ describe('useMessageListPagination', () => { // Mock query if needed const queryMock = jest.fn(); - channel.query = queryMock; + channel.query = queryMock as unknown as typeof channel.query; // Set up mocks const jumpToMessageFinishedMock = jest.fn(); diff --git a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx index 3fdadd4b15..04f2d71279 100644 --- a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx +++ b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx @@ -10,6 +10,7 @@ import { waitFor, within, } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { useChannelsContext } from '../../../contexts/channelsContext/ChannelsContext'; import { @@ -37,21 +38,22 @@ import { getTestClientWithUser } from '../../../mock-builders/mock'; import { Chat } from '../../Chat/Chat'; import { ChannelList } from '../ChannelList'; -const mockChannelSwipableWrapper = jest.fn(({ children }) => ( +const mockChannelSwipableWrapper = jest.fn(({ children }: { children: React.ReactNode }) => ( {children} )); jest.mock('../../ChannelPreview/ChannelSwipableWrapper', () => ({ - ChannelSwipableWrapper: (...args) => mockChannelSwipableWrapper(...args), + ChannelSwipableWrapper: (...args: unknown[]) => + (mockChannelSwipableWrapper as unknown as (...a: unknown[]) => React.ReactElement)(...args), })); /** * Custom ChannelPreview component used via WithComponents to verify channel rendering. * Receives { channel, muted, unread, lastMessage } from ChannelPreview. */ -const ChannelPreviewComponent = ({ channel }) => ( +const ChannelPreviewComponent = ({ channel }: { channel: ChannelType }) => ( - {channel.data?.name} + {(channel.data as { name?: string } | undefined)?.name} {channel.state.messages[0]?.text} ); @@ -73,9 +75,11 @@ const RefreshingProbe = () => { return {`${refreshing}`}; }; -const ChannelPreviewContent = ({ unread }) => {`${unread}`}; +const ChannelPreviewContent = ({ unread }: { unread?: number }) => ( + {`${unread}`} +); -let expectedChannelDetailsBottomSheetOverride; +let expectedChannelDetailsBottomSheetOverride: unknown; const ChannelDetailsBottomSheetProbe = () => { const { ChannelDetailsBottomSheet } = useComponentsContext(); return ( @@ -85,9 +89,13 @@ const ChannelDetailsBottomSheetProbe = () => { ); }; -class DeferredPromise { +class DeferredPromise { + promise: Promise; + resolve!: (value: T | PromiseLike) => void; + reject!: (reason?: unknown) => void; + constructor() { - this.promise = new Promise((resolve, reject) => { + this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); @@ -95,11 +103,11 @@ class DeferredPromise { } describe('ChannelList', () => { - let chatClient; - let testChannel1; - let testChannel2; - let testChannel3; - const props = { + let chatClient: StreamChat; + let testChannel1: ReturnType; + let testChannel2: ReturnType; + let testChannel3: ReturnType; + const props: Partial> = { filters: {}, }; @@ -163,7 +171,14 @@ describe('ChannelList', () => { screen.rerender( - + ['filters'] + } + /> , ); @@ -178,12 +193,17 @@ describe('ChannelList', () => { const deferredCallForFreshFilter = new DeferredPromise(); const staleFilter = { 'initial-filter': { a: { $gt: 'c' } } }; const freshFilter = { 'new-filter': { a: { $gt: 'c' } } }; - const createMockChannel = (id) => { + const createMockChannel = (id: string) => { const channel = generateChannel({ data: { name: id }, id, state: { latestMessages: [], members: {}, messages: [], setIsUpToDate: jest.fn() }, - }); + } as unknown as Parameters[0]) as unknown as { + countUnread: () => number; + messageComposer: { registerDraftEventSubscriptions: () => () => void }; + muteStatus: () => { muted: boolean }; + on: jest.Mock; + }; channel.countUnread = () => 0; channel.muteStatus = () => ({ muted: false }); channel.on = jest.fn(() => ({ unsubscribe: jest.fn() })); @@ -195,17 +215,22 @@ describe('ChannelList', () => { const staleChannel = [createMockChannel('stale-channel')]; const freshChannel = [createMockChannel('new-channel')]; const spy = jest.spyOn(chatClient, 'queryChannels'); - spy.mockImplementation((filters = {}) => { + spy.mockImplementation(((filters: Record = {}) => { if (Object.prototype.hasOwnProperty.call(filters, 'new-filter')) { return deferredCallForFreshFilter.promise; } return deferredCallForStaleFilter.promise; - }); + }) as unknown as typeof chatClient.queryChannels); const { rerender, queryByTestId } = render( - + ['filters'] + } + /> , ); @@ -225,7 +250,12 @@ describe('ChannelList', () => { rerender( - + ['filters'] + } + /> , ); @@ -406,13 +436,13 @@ describe('ChannelList', () => { const newMessage = sendNewMessageOnChannel3(); await waitFor(() => { - expect(screen.getByText(newMessage.text)).toBeTruthy(); + expect(screen.getByText(newMessage.text as string)).toBeTruthy(); }); const items = screen.getAllByLabelText('list-item'); await waitFor(() => { - expect(within(items[0]).getByText(newMessage.text)).toBeTruthy(); + expect(within(items[0]).getByText(newMessage.text as string)).toBeTruthy(); }); }); @@ -436,13 +466,13 @@ describe('ChannelList', () => { const newMessage = sendNewMessageOnChannel3(); await waitFor(() => { - expect(screen.getByText(newMessage.text)).toBeTruthy(); + expect(screen.getByText(newMessage.text as string)).toBeTruthy(); }); const items = screen.getAllByLabelText('list-item'); await waitFor(() => { - expect(within(items[0]).getByText(newMessage.text)).toBeTruthy(); + expect(within(items[0]).getByText(newMessage.text as string)).toBeTruthy(); }); }); @@ -462,13 +492,13 @@ describe('ChannelList', () => { const newMessage = sendNewMessageOnChannel3(); await waitFor(() => { - expect(screen.getByText(newMessage.text)).toBeTruthy(); + expect(screen.getByText(newMessage.text as string)).toBeTruthy(); }); const items = screen.getAllByLabelText('list-item'); await waitFor(() => { - expect(within(items[2]).getByText(newMessage.text)).toBeTruthy(); + expect(within(items[2]).getByText(newMessage.text as string)).toBeTruthy(); }); }); it('should call the `onNewMessage` function prop, if provided', async () => { @@ -485,7 +515,12 @@ describe('ChannelList', () => { expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); - act(() => dispatchMessageNewEvent(chatClient, testChannel2.channel)); + act(() => + dispatchMessageNewEvent( + chatClient, + testChannel2.channel as unknown as Parameters[1], + ), + ); await waitFor(() => { expect(onNewMessage).toHaveBeenCalledTimes(1); @@ -538,7 +573,12 @@ describe('ChannelList', () => { expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); - act(() => dispatchMessageNewEvent(chatClient, testChannel2.channel)); + act(() => + dispatchMessageNewEvent( + chatClient, + testChannel2.channel as unknown as Parameters[1], + ), + ); await waitFor(() => { expect(onNewMessage).toHaveBeenCalledTimes(1); @@ -884,7 +924,9 @@ describe('ChannelList', () => { expect(screen.getByTestId('refreshing').children[0]).toBe('false'); }); - chatClient.queryChannels = jest.fn(() => deferredPromise.promise); + chatClient.queryChannels = jest.fn( + () => deferredPromise.promise, + ) as unknown as typeof chatClient.queryChannels; act(() => dispatchConnectionChangedEvent(chatClient, false)); act(() => dispatchConnectionChangedEvent(chatClient, true)); diff --git a/package/src/components/ChannelList/__tests__/ChannelListView.test.tsx b/package/src/components/ChannelList/__tests__/ChannelListView.test.tsx index 73b800cf23..4ea001e435 100644 --- a/package/src/components/ChannelList/__tests__/ChannelListView.test.tsx +++ b/package/src/components/ChannelList/__tests__/ChannelListView.test.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { cleanup, render, waitFor } from '@testing-library/react-native'; +import type { StreamChat } from 'stream-chat'; +import type { ChannelsContextValue } from '../../../contexts/channelsContext/ChannelsContext'; import { ChannelsProvider } from '../../../contexts/channelsContext/ChannelsContext'; import { ChatContext, ChatProvider } from '../../../contexts/chatContext/ChatContext'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; @@ -13,7 +15,7 @@ import { Chat } from '../../Chat/Chat'; import { ChannelList } from '../ChannelList'; import { ChannelListView } from '../ChannelListView'; -let chatClient; +let chatClient: StreamChat; /** * Renders the full ChannelList (which now always uses ChannelListView internally). @@ -42,30 +44,38 @@ const noop = () => {}; * Renders ChannelListView directly with a mock ChannelsContext for testing * error and loading states. */ -const ComponentWithContextOverrides = ({ error, loadingChannels }) => ( +const ComponentWithContextOverrides = ({ + error, + loadingChannels, +}: { + error: boolean; + loadingChannels: boolean; +}) => ( {(context) => ( diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx index a023b9bc1e..908755696e 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import truncate from 'lodash/truncate'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; import { useMockedApis } from '../../../mock-builders/api/useMockedApis'; @@ -15,32 +16,34 @@ import { ChannelPreviewView } from '../ChannelPreviewView'; describe('ChannelPreviewView', () => { const clientUser = generateUser(); - let chatClient; - let channel; + let chatClient: StreamChat; + let channel: ChannelType | null; - const getComponent = (props = {}) => ( + const getComponent = (props: Partial> = {}) => ( )} {...props} /> ); - const initializeChannel = async (c) => { + const initializeChannel = async (c: ReturnType) => { useMockedApis(chatClient, [getOrCreateChannelApi(c)]); channel = chatClient.channel('messaging'); @@ -63,7 +66,7 @@ describe('ChannelPreviewView', () => { render( getComponent({ onSelect, - watchers: {}, + ...({ watchers: {} } as unknown as Partial>), }), ); @@ -101,7 +104,7 @@ describe('ChannelPreviewView', () => { ); render(getComponent()); - const expectedDisplayName = `${m1.user.name}, ${m2.user.name}, ${m3.user.name}`; + const expectedDisplayName = `${m1.user!.name}, ${m2.user!.name}, ${m3.user!.name}`; await waitFor(() => screen.queryByText(expectedDisplayName)); }); @@ -112,8 +115,10 @@ describe('ChannelPreviewView', () => { render( getComponent({ - latestMessage: message, - latestMessageLength: 6, + ...({ + latestMessage: message, + latestMessageLength: 6, + } as unknown as Partial>), }), ); diff --git a/package/src/components/Chat/__tests__/Chat.test.tsx b/package/src/components/Chat/__tests__/Chat.test.tsx index 44d1c049db..f814b28f39 100644 --- a/package/src/components/Chat/__tests__/Chat.test.tsx +++ b/package/src/components/Chat/__tests__/Chat.test.tsx @@ -162,8 +162,8 @@ describe('TranslationContext', () => { const i18nInstance = new Streami18n(); const { t, tDateTimeParser } = await i18nInstance.getTranslators(); - i18nInstance.t = () => 't'; - i18nInstance.tDateTimeParser = () => 'tDateTimeParser'; + i18nInstance.t = (() => 't') as unknown as typeof i18nInstance.t; + i18nInstance.tDateTimeParser = (() => 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; render( @@ -187,8 +187,8 @@ describe('TranslationContext', () => { let context; const i18nInstance = new Streami18n(); - i18nInstance.t = () => 't'; - i18nInstance.tDateTimeParser = () => 'tDateTimeParser'; + i18nInstance.t = (() => 't') as unknown as typeof i18nInstance.t; + i18nInstance.tDateTimeParser = (() => 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; const { rerender } = render( @@ -207,8 +207,9 @@ describe('TranslationContext', () => { const newI18nInstance = new Streami18n(); - newI18nInstance.t = () => 'newT'; - newI18nInstance.tDateTimeParser = () => 'newtDateTimeParser'; + newI18nInstance.t = (() => 'newT') as unknown as typeof newI18nInstance.t; + newI18nInstance.tDateTimeParser = (() => + 'newtDateTimeParser') as unknown as typeof newI18nInstance.tDateTimeParser; rerender( @@ -235,13 +236,13 @@ describe('TranslationContext', () => { let unsubscribeSpy; let listenersAfterInitialMount; - const initSpy = jest.spyOn(chatClientWithUser.offlineDb.syncManager, 'init'); + const initSpy = jest.spyOn(chatClientWithUser.offlineDb!.syncManager, 'init'); await waitFor(() => { // the unsubscribe fn changes during init(), so we keep a reference to the spy unsubscribeSpy = jest.spyOn( - chatClientWithUser.offlineDb.syncManager.connectionChangedListener, - 'unsubscribe', + chatClientWithUser.offlineDb!.syncManager.connectionChangedListener as object, + 'unsubscribe' as never, ); listenersAfterInitialMount = chatClientWithUser.listeners['connection.changed']; }); @@ -266,13 +267,13 @@ describe('TranslationContext', () => { let unsubscribeSpy; let listenersAfterInitialMount; - const initSpy = jest.spyOn(chatClientWithUser.offlineDb.syncManager, 'init'); + const initSpy = jest.spyOn(chatClientWithUser.offlineDb!.syncManager, 'init'); await waitFor(() => { // the unsubscribe fn changes during init(), so we keep a reference to the spy unsubscribeSpy = jest.spyOn( - chatClientWithUser.offlineDb.syncManager.connectionChangedListener, - 'unsubscribe', + chatClientWithUser.offlineDb!.syncManager.connectionChangedListener as object, + 'unsubscribe' as never, ); listenersAfterInitialMount = chatClientWithUser.listeners['connection.changed']; }); @@ -300,13 +301,13 @@ describe('TranslationContext', () => { const { rerender } = render(); let unsubscribeSpy; - const initSpy = jest.spyOn(chatClientWithUser.offlineDb.syncManager, 'init'); + const initSpy = jest.spyOn(chatClientWithUser.offlineDb!.syncManager, 'init'); await waitFor(() => { // the unsubscribe fn changes during init(), so we keep a reference to the spy unsubscribeSpy = jest.spyOn( - chatClientWithUser.offlineDb.syncManager.connectionChangedListener, - 'unsubscribe', + chatClientWithUser.offlineDb!.syncManager.connectionChangedListener as object, + 'unsubscribe' as never, ); }); diff --git a/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx b/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx index 9a87d7f7b8..6023a1238b 100644 --- a/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx @@ -70,19 +70,21 @@ describe('Message', () => { chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); renderMessage = (options, channelProps, componentOverrides) => render( ['value'] + } > {componentOverrides ? ( diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx index 115c505911..d0023a046e 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx @@ -1,7 +1,10 @@ import React from 'react'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { LocalMessage, StreamChat } from 'stream-chat'; +import type { DeepPartial } from '../../../../contexts/themeContext/ThemeContext'; +import type { Theme } from '../../../../contexts/themeContext/utils/theme'; import { defaultTheme } from '../../../../contexts/themeContext/utils/theme'; import { generateMessage, @@ -15,7 +18,7 @@ import { MessageAuthor } from '../MessageAuthor'; afterEach(cleanup); describe('MessageAuthor', () => { - let chatClient; + let chatClient: StreamChat; beforeEach(async () => { chatClient = await getTestClientWithUser({ id: 'me' }); @@ -27,8 +30,14 @@ describe('MessageAuthor', () => { user: { ...staticUser, image: undefined }, }); render( - - + }> + )} + message={message as unknown as LocalMessage} + /> , ); @@ -37,8 +46,14 @@ describe('MessageAuthor', () => { }); screen.rerender( - - + }> + )} + message={message as unknown as LocalMessage} + /> , ); @@ -52,11 +67,13 @@ describe('MessageAuthor', () => { }); screen.rerender( - + }> )} + message={staticMessage as unknown as LocalMessage} showAvatar /> , diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx index 5a2195d00a..acbd146bc0 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; @@ -22,10 +23,16 @@ import { getTestClientWithUser } from '../../../../mock-builders/mock'; import { Channel } from '../../../Channel/Channel'; import { Chat } from '../../../Chat/Chat'; import { Message } from '../../Message'; + +const toLocalMessage = (m: unknown): LocalMessage => m as LocalMessage; + describe('MessageContent', () => { - let channel; - let chatClient; - let renderMessage; + let channel: ChannelType; + let chatClient: StreamChat; + let renderMessage: (options: { + message: unknown; + [key: string]: unknown; + }) => ReturnType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ user })]; @@ -39,13 +46,18 @@ describe('MessageContent', () => { chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); renderMessage = (options) => render( - + )} + /> , ); @@ -119,7 +131,7 @@ describe('MessageContent', () => { - + @@ -143,7 +155,7 @@ describe('MessageContent', () => { - + @@ -170,7 +182,7 @@ describe('MessageContent', () => { }} > - + @@ -198,7 +210,7 @@ describe('MessageContent', () => { }} > - + @@ -227,7 +239,7 @@ describe('MessageContent', () => { }} > - + @@ -252,7 +264,10 @@ describe('MessageContent', () => { }} > - + @@ -441,7 +456,9 @@ describe('MessageContent', () => { const user = generateUser(); const reaction = generateReaction(); const message = generateMessage({ - reaction_groups: { [reaction.type]: reaction }, + reaction_groups: { [reaction.type]: reaction } as unknown as ReturnType< + typeof generateMessage + >['reaction_groups'], user, }); @@ -449,7 +466,13 @@ describe('MessageContent', () => { - + )} + /> , diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx index 21f212cabf..1fa2d36ab3 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx @@ -41,7 +41,7 @@ describe('MessageItemView', () => { chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); renderMessage = (options, channelProps, componentOverrides) => render( diff --git a/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx index c8b28a20a7..00d513c323 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx @@ -1,8 +1,11 @@ import React from 'react'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { LocalMessage } from 'stream-chat'; +import type { DeepPartial } from '../../../../contexts/themeContext/ThemeContext'; import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext'; +import type { Theme } from '../../../../contexts/themeContext/utils/theme'; import { defaultTheme } from '../../../../contexts/themeContext/utils/theme'; import { generateMessage, @@ -21,8 +24,8 @@ describe('MessagePinnedHeader', () => { pinned: true, }); render( - - + }> + , ); @@ -31,8 +34,8 @@ describe('MessagePinnedHeader', () => { }); screen.rerender( - - + }> + , ); @@ -42,8 +45,8 @@ describe('MessagePinnedHeader', () => { }); screen.rerender( - - + }> + , ); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx index 41207aa481..b41fc08d2e 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx @@ -1,9 +1,13 @@ import React from 'react'; import { cleanup, render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import type { LocalMessage } from 'stream-chat'; +import type { DeepPartial } from '../../../../contexts/themeContext/ThemeContext'; import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext'; +import type { Theme } from '../../../../contexts/themeContext/utils/theme'; import { defaultTheme } from '../../../../contexts/themeContext/utils/theme'; +import type { TranslationContextValue } from '../../../../contexts/translationContext/TranslationContext'; import { TranslationProvider } from '../../../../contexts/translationContext/TranslationContext'; import { generateMessage } from '../../../../mock-builders/generator/message'; import { generateStaticUser, generateUser } from '../../../../mock-builders/generator/user'; @@ -23,14 +27,16 @@ describe('MessageReplies', () => { user: staticUser, }); render( - - + + }> )} + message={message as unknown as LocalMessage} /> , @@ -50,14 +56,16 @@ describe('MessageReplies', () => { }); screen.rerender( - - + + }> )} + message={message2 as unknown as LocalMessage} /> , @@ -80,13 +88,15 @@ describe('MessageReplies', () => { user, }); render( - - + + }> null} + {...({ + alignment: 'right', + groupStyles: ['bottom'], + onPress: () => null, + } as unknown as React.ComponentProps)} + message={message as unknown as LocalMessage} /> , @@ -102,13 +112,15 @@ describe('MessageReplies', () => { }); screen.rerender( - - + + }> null} + {...({ + alignment: 'right', + groupStyles: ['bottom'], + onPress: () => null, + } as unknown as React.ComponentProps)} + message={message2 as unknown as LocalMessage} threadList /> diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx index dbf94316ad..5e7a795028 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { cleanup, render, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat'; import { Channel } from '../../..'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; @@ -15,9 +16,9 @@ import { Streami18n } from '../../../../utils/i18n/Streami18n'; import { Chat } from '../../../Chat/Chat'; import { MessageStatus } from '../MessageStatus'; -let chatClient; -let i18nInstance; -let channel; +let chatClient: StreamChat; +let i18nInstance: Streami18n; +let channel: ChannelType; describe('MessageStatus', () => { const user1 = generateUser({ id: 'id1', name: 'name1' }); const user2 = generateUser({ id: 'id2', name: 'name2' }); @@ -29,7 +30,6 @@ describe('MessageStatus', () => { generateMember({ user: user3 }), ]; beforeAll(() => { - id = 'testID'; i18nInstance = new Streami18n(); }); beforeEach(async () => { @@ -41,13 +41,18 @@ describe('MessageStatus', () => { chatClient = await getTestClientWithUser(user1); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); - channel.state.members = Object.fromEntries(members.map((member) => [member.user_id, member])); + channel.state.members = Object.fromEntries( + members.map((member) => [member.user_id, member]), + ) as unknown as typeof channel.state.members; }); afterEach(cleanup); - renderMessageStatus = (options, channelProps) => + const renderMessageStatus = ( + options: Partial>, + channelProps?: Partial>, + ) => render( @@ -58,14 +63,19 @@ describe('MessageStatus', () => { , ); - it.each('should render message status with read by container', async () => { + // NOTE: Original source had `it.each('string', async () => { ... })` which was a + // malformed `it.each` call (string-as-iterable), so Jest never actually executed + // the test body. Preserving that behavior here by skipping: re-enabling would + // introduce a new failing test assertion that does not match current component + // output (component renders icons, not text readCount). See migration PR notes. + it.skip('should render message status with read by container', async () => { const user = generateUser(); const message = generateMessage({ user }); const readBy = 2; const { getByText, rerender, toJSON } = renderMessageStatus({ deliveredToCount: 2, - message, + message: message as unknown as LocalMessage, readBy, }); @@ -74,13 +84,13 @@ describe('MessageStatus', () => { }); const staticUser = generateStaticUser(0); - const staticMessage = generateMessage({ readBy, user: staticUser }); + const staticMessage = generateMessage({ user: staticUser }); rerender( - + , @@ -97,14 +107,14 @@ describe('MessageStatus', () => { [2, 2, 'received', 'Read'], [1, 1, 'received', 'Sent'], [2, 1, 'received', 'Delivered'], - ])( + ] as [number, number, string, string][])( 'should render message status with %s container when deliveredToCount is %s and readBy is %s and status is %s', async (deliveredToCount, readBy, status, accessibilityLabel) => { const user = generateUser(); const message = generateMessage({ user }); const { getByLabelText } = renderMessageStatus({ deliveredToCount, - message: { ...message, status }, + message: { ...message, status } as unknown as LocalMessage, readBy, }); await waitFor(() => { diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx index 0caded18fc..c834cfa7b0 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx @@ -39,7 +39,7 @@ describe('MessageTextContainer', () => { await waitFor(() => { expect(getByTestId('message-text-container')).toBeTruthy(); - expect(getByText(message.text)).toBeTruthy(); + expect(getByText(message.text as string)).toBeTruthy(); }); rerender( @@ -57,7 +57,7 @@ describe('MessageTextContainer', () => { await waitFor(() => { expect(getByTestId('message-text-container')).toBeTruthy(); expect(getByTestId('message-text')).toBeTruthy(); - expect(getByText(message.text)).toBeTruthy(); + expect(getByText(message.text as string)).toBeTruthy(); }); const staticMessage = generateStaticMessage('Hello World', { @@ -87,7 +87,9 @@ describe('MessageTextContainer', () => { const mockedChannel = generateChannelResponse({ id: 'chans', - messages: [message], + messages: [message] as unknown as NonNullable< + Parameters[0] + >['messages'], }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); diff --git a/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx b/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx index 3e462f9caa..e2c19e1f28 100644 --- a/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx @@ -33,7 +33,7 @@ describe('ReactionListBottom', () => { chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); renderMessage = (options, channelProps) => render( @@ -56,7 +56,9 @@ describe('ReactionListBottom', () => { const user = generateUser(); const reaction = generateReaction(); const message = generateMessage({ - reaction_groups: { [reaction.type]: reaction }, + reaction_groups: { [reaction.type]: reaction } as unknown as ReturnType< + typeof generateMessage + >['reaction_groups'], user, }); @@ -71,7 +73,9 @@ describe('ReactionListBottom', () => { const user = generateUser(); const reaction = generateReaction(); const message = generateMessage({ - reaction_groups: { [reaction.type]: reaction }, + reaction_groups: { [reaction.type]: reaction } as unknown as ReturnType< + typeof generateMessage + >['reaction_groups'], user, }); @@ -145,7 +149,9 @@ describe('ReactionListBottom', () => { const user = generateUser(); const reaction = generateReaction(); const message = generateMessage({ - reaction_groups: { [reaction.type]: reaction }, + reaction_groups: { [reaction.type]: reaction } as unknown as ReturnType< + typeof generateMessage + >['reaction_groups'], user, }); diff --git a/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx b/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx index e6007a780a..c1f34cbaf4 100644 --- a/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx @@ -34,7 +34,7 @@ describe('ReactionListTop', () => { chatClient = await getTestClientWithUser(user); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel('messaging', mockedChannel.channel.id); renderMessage = (options, channelProps) => render( diff --git a/package/src/components/MessageList/__tests__/MessageList.test.tsx b/package/src/components/MessageList/__tests__/MessageList.test.tsx index f1e4360e04..54cab06c78 100644 --- a/package/src/components/MessageList/__tests__/MessageList.test.tsx +++ b/package/src/components/MessageList/__tests__/MessageList.test.tsx @@ -38,7 +38,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const { getByText, queryAllByTestId } = render( @@ -56,7 +56,7 @@ describe('MessageList', () => { await waitFor(() => { expect(queryAllByTestId('scroll-to-bottom-button')).toHaveLength(0); - expect(getByText(newMessage.text)).toBeTruthy(); + expect(getByText(newMessage.text as string)).toBeTruthy(); }); }, 10000); @@ -73,7 +73,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const { getByTestId } = render( @@ -105,7 +105,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const { getByTestId, queryByTestId } = render( @@ -133,7 +133,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const { getByTestId, queryAllByTestId } = render( @@ -165,7 +165,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const { getByTestId } = render( @@ -192,7 +192,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const { getByTestId, getByText, queryAllByTestId } = render( @@ -216,7 +216,7 @@ describe('MessageList', () => { it('should scroll to a message even if out of the loaded window', async () => { const user1 = generateUser(); - const mockedLongMessagesList = []; + const mockedLongMessagesList: ReturnType[] = []; // we need a long enough list to make sure elements aren't preloaded by the underlying FlatList for (let i = 0; i <= 150; i += 1) { mockedLongMessagesList.push(generateMessage({ timestamp: new Date(), user: user1 })); @@ -233,7 +233,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); render( @@ -247,8 +247,8 @@ describe('MessageList', () => { ); await waitFor(() => { - expect(screen.getByText(targetedMessageText)).toBeOnTheScreen(); - expect(() => screen.getByText(latestMessageText)).toThrow(); + expect(screen.getByText(targetedMessageText as string)).toBeOnTheScreen(); + expect(() => screen.getByText(latestMessageText as string)).toThrow(); }); }); @@ -271,7 +271,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: user1.id }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); channel.state = { @@ -279,7 +279,7 @@ describe('MessageList', () => { latestMessages: [], messages, read: read_data, - }; + } as unknown as typeof channel.state; const { queryByLabelText } = render( @@ -308,7 +308,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: user1.id }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const channelUnreadState = { @@ -320,13 +320,16 @@ describe('MessageList', () => { ...channelInitialState, latestMessages: [], messages, - }; + } as unknown as typeof channel.state; + const messageListProps = { channelUnreadState } as unknown as React.ComponentProps< + typeof MessageList + >; const { queryByLabelText } = render( - + , @@ -345,7 +348,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: user.id }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const user2 = generateUser(); @@ -382,7 +385,7 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: user.id }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const targetedMessage = messages[15].id; @@ -391,7 +394,7 @@ describe('MessageList', () => { ...channelInitialState, latestMessages: [], messages, - }; + } as unknown as typeof channel.state; const flatListRefMock = jest .spyOn(FlatList.prototype, 'scrollToIndex') @@ -428,17 +431,17 @@ describe('MessageList', () => { const chatClient = await getTestClientWithUser({ id: user.id }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); - const targetedMessage = 21; + const targetedMessage = '21'; const setTargetedMessage = jest.fn(); channel.state = { ...channelInitialState, latestMessages: [], messages, - }; + } as unknown as typeof channel.state; const loadChannelAroundMessage = jest.fn(() => Promise.resolve()); @@ -499,7 +502,7 @@ describe('MessageList pagination', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const loadMoreRecent = jest.fn(() => Promise.resolve()); @@ -541,7 +544,7 @@ describe('MessageList pagination', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const loadMore = jest.fn(() => Promise.resolve()); @@ -586,18 +589,23 @@ describe('MessageList pagination', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); channel.state = { ...channelInitialState, latestMessages: [], members: Object.fromEntries( - Array.from({ length: 10 }, (_, i) => [i, generateMember({ id: i })]), + Array.from({ length: 10 }, (_, i) => [ + i, + generateMember({ user_id: String(i) } as unknown as Partial< + Parameters[0] + >), + ]), ), - messages: Array.from({ length: 10 }, (_, i) => generateMessage({ id: i })), + messages: Array.from({ length: 10 }, (_, i) => generateMessage({ id: String(i) })), messageSets: [{ isCurrent: true, isLatest: true }], - }; + } as unknown as typeof channel.state; const loadLatestMessages = jest.fn(() => Promise.resolve()); mockedHook({ loadLatestMessages }); diff --git a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx index 788c1e51ea..0bea239576 100644 --- a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx +++ b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { cleanup, fireEvent, render, waitFor } from '@testing-library/react-native'; import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; +import type { TranslationContextValue } from '../../../contexts/translationContext/TranslationContext'; import { TranslationProvider } from '../../../contexts/translationContext/TranslationContext'; import { Streami18n } from '../../../utils/i18n/Streami18n'; import { ScrollToBottomButton } from '../ScrollToBottomButton'; @@ -20,7 +21,7 @@ describe('ScrollToBottomButton', () => { const translators = await i18nInstance.getTranslators(); const { queryByTestId } = render( - + null} showNotification={false} /> , @@ -36,7 +37,7 @@ describe('ScrollToBottomButton', () => { const translators = await i18nInstance.getTranslators(); const { queryByTestId } = render( - + null} showNotification={true} /> , @@ -53,7 +54,7 @@ describe('ScrollToBottomButton', () => { const onPress = jest.fn(); const { getByTestId } = render( - + , @@ -63,16 +64,18 @@ describe('ScrollToBottomButton', () => { }); it('should display the unread count', async () => { - const t = jest.fn((key) => key); + const t = jest.fn((key: string) => key); const i18nInstance = new Streami18n(); const translators = await i18nInstance.getTranslators(); const { getByTestId, getByText } = render( - + )} onPress={() => null} showNotification={true} - t={t} unreadCount={3} /> @@ -89,7 +92,7 @@ describe('ScrollToBottomButton', () => { const translators = await i18nInstance.getTranslators(); const { toJSON } = render( - + null} showNotification={true} /> , diff --git a/package/src/components/Thread/__tests__/Thread.test.tsx b/package/src/components/Thread/__tests__/Thread.test.tsx index 185e3eeaa4..8bd1065c76 100644 --- a/package/src/components/Thread/__tests__/Thread.test.tsx +++ b/package/src/components/Thread/__tests__/Thread.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat'; import { v5 as uuidv5 } from 'uuid'; import { AttachmentPickerProvider } from '../../../contexts/attachmentPickerContext/AttachmentPickerContext'; @@ -23,7 +24,17 @@ import { Thread } from '../Thread'; const StreamReactNativeNamespace = '9b244ee4-7d69-4d7b-ae23-cf89e9f7b035'; -const renderComponent = ({ chatClient, channel, props, thread }) => { +const renderComponent = ({ + chatClient, + channel, + props, + thread, +}: { + channel: ChannelType; + chatClient: StreamChat; + props?: Partial>; + thread: LocalMessage; +}) => { return render( @@ -36,8 +47,8 @@ const renderComponent = ({ chatClient, channel, props, thread }) => { }; describe('Thread', () => { - let chatClient; - let channel; + let chatClient: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: client, channels } = await initiateClientWithChannels(); @@ -55,7 +66,7 @@ describe('Thread', () => { const thread = generateMessage({ cid, text: 'Thread Message Text' }); const parent_id = thread.id; const props = { - thread, + thread: thread as unknown as LocalMessage, }; const threadResponses = [ @@ -64,9 +75,11 @@ describe('Thread', () => { generateMessage({ cid, parent_id }), ]; - channel.state.addMessagesSorted(threadResponses); + channel.state.addMessagesSorted( + threadResponses as unknown as Parameters[0], + ); - renderComponent({ channel, chatClient, props, thread }); + renderComponent({ channel, chatClient, props, thread: thread as unknown as LocalMessage }); const { getAllByText, getByText, queryByText } = screen; @@ -122,19 +135,37 @@ describe('Thread', () => { const chatClient = await getTestClientWithUser({ id: 'testID2' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.query(); - channel.state.addMessagesSorted(threadResponses); + channel.state.addMessagesSorted( + threadResponses as unknown as Parameters[0], + ); - let setLastRead; + let setLastRead: ((date?: Date) => void) | undefined; const { getByText, toJSON } = render( - - - + ['value'] + } + > + ['value'] + } + > + {(c) => { setLastRead = c.setLastRead; @@ -154,9 +185,13 @@ describe('Thread', () => { expect(getByText('Message6')).toBeTruthy(); }); - act(() => setLastRead(new Date('2020-08-17T18:08:03.196Z'))); + act(() => setLastRead!(new Date('2020-08-17T18:08:03.196Z'))); - const snapshot = toJSON(); + const snapshot = toJSON() as unknown as { + children: Array<{ + children: Array<{ children: Array<{ props: { ListFooterComponent: unknown } }> }>; + }>; + }; snapshot.children[0].children[0].children[0].props.ListFooterComponent = null; await waitFor(() => { From 316b8c0b306648e5345afd3acf21057c4c94ed09 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 13:50:52 +0200 Subject: [PATCH 06/15] refactor(tests): annotate attachment, message-input, and remaining tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Type annotations for ~32 remaining test files across attachment, message-input, message-menu, channel-preview, image-gallery, hooks, and context folders. No behavior changes. Patterns applied (same as the previous commit): - `let client: StreamChat; let channel: Channel;` at describe scope. - `{...} as unknown as XContextValue` for partial context mocks. - `renderComponent` helpers typed with `ComponentProps`. - `MessageResponse` → `LocalMessage` via local wrapper casts. - Replace bogus `as unknown as FileUpload` (never imported — leftover silent bug from .js) with the real component prop type: `LocalAudioAttachment` / `LocalVoiceRecordingAttachment` from `stream-chat` (see `AudioAttachmentUploadPreview.tsx:5`). - Attachment mocks with `localMetadata` typed as `LocalAttachment` (from `stream-chat`) rather than `Partial`. Notable: `AudioAttachmentUploadPreviewNative.test.tsx` and `*Expo.test.tsx` already used `describe.skip` at the top (pre-migration behavior: their mock props don't match the current component API). Kept the skip and loosened helper-arg types to `unknown` so the (unreachable) test bodies type-check. Filing these as follow-up to properly un-skip and update. Takes test:typecheck 227 → 69 (−158). All 69 remaining errors are in the two offline-support helpers (`offline-feature.tsx`, `optimistic-update.tsx`) — handled in the next commit. Full test suite: 752 passed, 14 skipped; only the pre-existing SQLite-isolation flake in `offline-support/index.test.ts` fails. No regressions. --- .../Attachment/__tests__/Attachment.test.tsx | 23 +++--- .../Attachment/__tests__/Gallery.test.tsx | 13 +++- .../Attachment/__tests__/Giphy.test.tsx | 51 +++++++++--- .../Attachment/__tests__/buildGallery.test.ts | 6 +- .../__tests__/AutoCompleteInput.test.tsx | 12 +-- .../__tests__/useChannelActionItems.test.tsx | 12 +-- .../useChannelActionItemsById.test.tsx | 4 +- .../__tests__/useChannelUpdated.test.tsx | 31 ++++---- .../ChannelDetailsBottomSheet.test.tsx | 11 ++- .../__tests__/ChannelPreview.test.tsx | 31 +++++--- .../__tests__/ChannelSwipableWrapper.test.tsx | 3 +- .../useChannelPreviewDisplayPresence.test.tsx | 2 +- .../__tests__/ImageGalleryGrid.test.tsx | 3 +- .../__tests__/ImageGalleryHeader.test.tsx | 9 ++- .../__tests__/ImageGalleryHeader.test.tsx | 13 ++-- .../useShouldUseOverlayStyles.test.tsx | 4 +- .../AttachmentUploadPreviewList.test.tsx | 35 +++++++-- .../AudioAttachmentUploadPreview.test.tsx | 23 ++++-- .../AudioAttachmentUploadPreviewExpo.test.tsx | 62 +++++++++++---- ...udioAttachmentUploadPreviewNative.test.tsx | 74 ++++++++++++++---- .../__tests__/MessageComposer.test.tsx | 28 ++++--- .../SendMessageDisallowedIndicator.test.tsx | 78 +++++++++++-------- .../__tests__/MessageSystem.test.tsx | 26 +++++-- .../__tests__/TypingIndicator.test.tsx | 10 ++- .../__tests__/useMessageList.test.tsx | 4 +- .../__tests__/MessageActionList.test.tsx | 5 +- .../__tests__/MessageActionListItem.test.tsx | 1 + .../MessageUserReactionsAvatar.test.tsx | 16 +++- .../MessageUserReactionsItem.test.tsx | 3 +- .../__tests__/SwipableWrapper.test.tsx | 2 +- .../__tests__/filePickers.test.tsx | 16 +++- .../__tests__/sendMessage.test.tsx | 2 +- .../MessageOverlayHostLayer.test.tsx | 4 +- .../__tests__/useTranslatedMessage.test.tsx | 8 +- 34 files changed, 425 insertions(+), 200 deletions(-) diff --git a/package/src/components/Attachment/__tests__/Attachment.test.tsx b/package/src/components/Attachment/__tests__/Attachment.test.tsx index 8e1d28ff0f..9d6df753b2 100644 --- a/package/src/components/Attachment/__tests__/Attachment.test.tsx +++ b/package/src/components/Attachment/__tests__/Attachment.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { render, waitFor } from '@testing-library/react-native'; +import type { LocalMessage } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; +import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; import { MessageProvider } from '../../../contexts/messageContext/MessageContext'; +import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext'; import { MessagesProvider } from '../../../contexts/messagesContext/MessagesContext'; import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { @@ -24,18 +27,20 @@ jest.mock('../../../native.ts', () => ({ isSoundPackageAvailable: jest.fn(() => false), })); -const getAttachmentComponent = (props) => { - const message = generateMessage(); +const getAttachmentComponent = (props: ComponentProps) => { + const message = generateMessage() as unknown as LocalMessage; return ( - + diff --git a/package/src/components/Attachment/__tests__/Gallery.test.tsx b/package/src/components/Attachment/__tests__/Gallery.test.tsx index baed13ea4b..a71fef54f6 100644 --- a/package/src/components/Attachment/__tests__/Gallery.test.tsx +++ b/package/src/components/Attachment/__tests__/Gallery.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Attachment, ChannelResponse } from 'stream-chat'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; @@ -31,7 +32,10 @@ describe('Gallery', () => { const user1 = generateUser(); - const getComponent = async (attachments = [], channelProps = {}) => { + const getComponent = async ( + attachments: Attachment[] = [], + channelProps: Partial> = {}, + ) => { const chatClient = await getTestClientWithUser({ id: 'testID' }); const mockedChannel = generateChannelResponse({ @@ -39,7 +43,10 @@ describe('Gallery', () => { messages: [generateMessage({ attachments, user: user1 })], }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel( + 'messaging', + (mockedChannel.channel as unknown as ChannelResponse).id, + ); await channel.watch(); return ( diff --git a/package/src/components/Attachment/__tests__/Giphy.test.tsx b/package/src/components/Attachment/__tests__/Giphy.test.tsx index a9c24ed483..26855d427f 100644 --- a/package/src/components/Attachment/__tests__/Giphy.test.tsx +++ b/package/src/components/Attachment/__tests__/Giphy.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { act, @@ -9,8 +9,16 @@ import { userEvent, waitFor, } from '@testing-library/react-native'; - +import type { + Channel as ChannelType, + ChannelResponse, + LocalMessage, + StreamChat, +} from 'stream-chat'; + +import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; import { MessageProvider } from '../../../contexts/messageContext/MessageContext'; +import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext'; import { MessagesProvider } from '../../../contexts/messagesContext/MessagesContext'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; @@ -38,21 +46,34 @@ const streami18n = new Streami18n({ describe('Giphy', () => { const lightTheme = mergeThemes({ scheme: 'light' }); - const getAttachmentComponent = (props, messageContextValue = {}) => { - const message = generateMessage(); + const getAttachmentComponent = ( + props: Record, + messageContextValue: Partial = {}, + ) => { + const message = generateMessage() as unknown as LocalMessage; return ( - - - + + + )} /> ); }; - let chatClient; - let channel; - let attachment; + let chatClient: StreamChat; + let channel: ChannelType; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let attachment: any; const actions = [ { name: 'image_action', text: 'Send', value: 'send' }, @@ -91,7 +112,10 @@ describe('Giphy', () => { chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - channel = chatClient.channel('messaging', mockedChannel.id); + channel = chatClient.channel( + 'messaging', + (mockedChannel.channel as unknown as ChannelResponse).id, + ); await channel.watch(); }; @@ -321,7 +345,10 @@ describe('Giphy', () => { const chatClient = await getTestClientWithUser({ id: 'testID' }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); - const channel = chatClient.channel('messaging', mockedChannel.id); + const channel = chatClient.channel( + 'messaging', + (mockedChannel.channel as unknown as ChannelResponse).id, + ); await channel.watch(); render( diff --git a/package/src/components/Attachment/__tests__/buildGallery.test.ts b/package/src/components/Attachment/__tests__/buildGallery.test.ts index eda9ee915c..3e81ea8bda 100644 --- a/package/src/components/Attachment/__tests__/buildGallery.test.ts +++ b/package/src/components/Attachment/__tests__/buildGallery.test.ts @@ -1,3 +1,5 @@ +import type { Attachment } from 'stream-chat'; + import { generateImageAttachment } from '../../../mock-builders/generator/attachment'; import { buildGallery } from '../utils/buildGallery/buildGallery'; @@ -20,7 +22,7 @@ describe('buildGallery', () => { ]; imageSizeTestCases.forEach((size) => { - const attachments = []; + const attachments: Attachment[] = []; for (let numOfImages = 0; numOfImages < 4; numOfImages++) { const a1 = generateImageAttachment({ ...size, @@ -77,7 +79,7 @@ describe('buildGallery', () => { }); it('gallery size should default to gridHeight and gridWidth if original image size is unavailable', () => { - const attachments = []; + const attachments: Attachment[] = []; for (let numOfImages = 0; numOfImages < 4; numOfImages++) { // During each iteration, size of attachments goes up. attachments.push(generateImageAttachment()); diff --git a/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx b/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx index 945581876e..879a798f51 100644 --- a/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx +++ b/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx @@ -43,7 +43,7 @@ describe('AutoCompleteInput', () => { const { queryByTestId } = screen; - const input = queryByTestId('auto-complete-text-input'); + const input = queryByTestId('auto-complete-text-input')!; await waitFor(() => { expect(input).toBeTruthy(); @@ -60,7 +60,7 @@ describe('AutoCompleteInput', () => { const { queryByTestId } = screen; - const input = queryByTestId('auto-complete-text-input'); + const input = queryByTestId('auto-complete-text-input')!; await waitFor(() => { expect(input.props.editable).toBeFalsy(); @@ -78,7 +78,7 @@ describe('AutoCompleteInput', () => { const { queryByTestId } = screen; - const input = queryByTestId('auto-complete-text-input'); + const input = queryByTestId('auto-complete-text-input')!; await waitFor(() => { expect(input.props.maxLength).toBe(10); @@ -97,7 +97,7 @@ describe('AutoCompleteInput', () => { const { queryByTestId } = screen; - const input = queryByTestId('auto-complete-text-input'); + const input = queryByTestId('auto-complete-text-input')!; act(() => { fireEvent.changeText(input, 'hello'); @@ -125,7 +125,7 @@ describe('AutoCompleteInput', () => { const { queryByTestId } = screen; - const input = queryByTestId('auto-complete-text-input'); + const input = queryByTestId('auto-complete-text-input')!; act(() => { fireEvent(input, 'selectionChange', { @@ -155,7 +155,7 @@ describe('AutoCompleteInput', () => { const { queryByTestId } = screen; - const input = queryByTestId('auto-complete-text-input'); + const input = queryByTestId('auto-complete-text-input')!; await waitFor(() => { expect(input.props.placeholder).toBe(data.result); diff --git a/package/src/components/ChannelList/hooks/__tests__/useChannelActionItems.test.tsx b/package/src/components/ChannelList/hooks/__tests__/useChannelActionItems.test.tsx index f0750915b3..57b5af4dd2 100644 --- a/package/src/components/ChannelList/hooks/__tests__/useChannelActionItems.test.tsx +++ b/package/src/components/ChannelList/hooks/__tests__/useChannelActionItems.test.tsx @@ -149,7 +149,7 @@ describe('getChannelActionItems', () => { isDirectChat: false, isPinned: false, muteActive: false, - t: (value) => value, + t: ((value: string) => value) as TranslationContextValue['t'], }); const actionItems = getChannelActionItems({ context: { @@ -159,7 +159,7 @@ describe('getChannelActionItems', () => { isDirectChat: false, isPinned: false, muteActive: false, - t: (value) => value, + t: ((value: string) => value) as TranslationContextValue['t'], }, defaultItems, }); @@ -186,7 +186,7 @@ describe('getChannelActionItems', () => { isDirectChat: true, isPinned: false, muteActive: true, - t: (value) => value, + t: ((value: string) => value) as TranslationContextValue['t'], }); expect(actionItems.map((item) => item.id)).toEqual(['mute', 'block', 'leave', 'deleteChannel']); @@ -213,7 +213,7 @@ describe('getChannelActionItems', () => { isDirectChat: false, isPinned: false, muteActive: false, - t: (value) => value, + t: ((value: string) => value) as TranslationContextValue['t'], }); expect(actionItems.map((item) => item.id)).toEqual(['mute', 'leave']); @@ -228,7 +228,7 @@ describe('getChannelActionItems', () => { isDirectChat: false, isPinned: false, muteActive: true, - t: (value) => value, + t: ((value: string) => value) as TranslationContextValue['t'], }); expect(actionItems[0].action).toBe(channelActions.unmuteChannel); @@ -251,7 +251,7 @@ describe('getChannelActionItems', () => { isDirectChat: false, isPinned: false, muteActive: false, - t: (value) => value, + t: ((value: string) => value) as TranslationContextValue['t'], }); const deleteItem = actionItems.find((item) => item.id === 'deleteChannel'); diff --git a/package/src/components/ChannelList/hooks/__tests__/useChannelActionItemsById.test.tsx b/package/src/components/ChannelList/hooks/__tests__/useChannelActionItemsById.test.tsx index 4e52742590..36c9fcdaf9 100644 --- a/package/src/components/ChannelList/hooks/__tests__/useChannelActionItemsById.test.tsx +++ b/package/src/components/ChannelList/hooks/__tests__/useChannelActionItemsById.test.tsx @@ -21,7 +21,7 @@ describe('useChannelActionItemsById', () => { const channelActionItems: useChannelActionItemsModule.ChannelActionItem[] = [ { action: jest.fn(), - Icon: <>, + Icon: () => <>, id: 'pin', label: '', placement: 'both', @@ -29,7 +29,7 @@ describe('useChannelActionItemsById', () => { }, { action: jest.fn(), - Icon: <>, + Icon: () => <>, id: 'deleteChannel', label: '', placement: 'both', 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 070463c5a9..efdb508d73 100644 --- a/package/src/components/ChannelList/hooks/listeners/__tests__/useChannelUpdated.test.tsx +++ b/package/src/components/ChannelList/hooks/listeners/__tests__/useChannelUpdated.test.tsx @@ -4,6 +4,7 @@ import { Image, Text } from 'react-native'; import { act, render, waitFor } from '@testing-library/react-native'; import type { Channel, ChannelResponse, Event, StreamChat } from 'stream-chat'; +import type { ChatContextValue } from '../../../../../contexts/chatContext/ChatContext'; import { ChatContext, useChannelUpdated } from '../../../../../index'; describe('useChannelUpdated', () => { @@ -33,16 +34,16 @@ describe('useChannelUpdated', () => { } as unknown as StreamChat; const TestComponent = () => { - const [channels, setChannels] = useState([mockChannel]); + const [channels, setChannels] = useState([mockChannel]); useChannelUpdated({ setChannels }); if ( channels && channels[0].data?.own_capabilities && - Object.keys(channels[0].data?.own_capabilities as { [key: string]: boolean }).includes( - 'send_messages', - ) + Object.keys( + channels[0].data?.own_capabilities as unknown as { [key: string]: boolean }, + ).includes('send_messages') ) { return Send messages enabled; } @@ -53,16 +54,18 @@ describe('useChannelUpdated', () => { const { getByText } = await waitFor(() => render( null, - }} + value={ + { + appSettings: null, + client: mockClient, + connectionRecovering: false, + enableOfflineSupport: false, + ImageComponent: Image, + isOnline: true, + mutedUsers: [], + setActiveChannel: () => null, + } as unknown as ChatContextValue + } > , diff --git a/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx index 2f2b7b11d9..d3f3747c2c 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx @@ -10,10 +10,13 @@ import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelAction import type { ChannelDetailsHeaderProps } from '../ChannelDetailsBottomSheet'; import { ChannelDetailsBottomSheet } from '../ChannelDetailsBottomSheet'; -const mockStreamBottomSheetModalFlatList = jest.fn(() => null); +const mockStreamBottomSheetModalFlatList = jest.fn( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_props: Record) => null, +); jest.mock('../../UIComponents/StreamBottomSheetModalFlatList', () => ({ - StreamBottomSheetModalFlatList: (...args: unknown[]) => + StreamBottomSheetModalFlatList: (...args: [Record]) => mockStreamBottomSheetModalFlatList(...args), })); @@ -73,7 +76,9 @@ describe('ChannelDetailsBottomSheet', () => { ); expect(mockStreamBottomSheetModalFlatList).toHaveBeenCalled(); - const flatListProps = mockStreamBottomSheetModalFlatList.mock.calls[0]?.[0]; + const flatListProps = ( + mockStreamBottomSheetModalFlatList.mock.calls[0] as unknown as [Record] + )?.[0]; expect(flatListProps).toEqual( expect.objectContaining({ onEndReached, diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx index fbf44b83ac..f83f64070d 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx @@ -41,7 +41,8 @@ const mockChannelSwipableWrapper = jest.fn(({ children }: React.PropsWithChildre )); jest.mock('../ChannelSwipableWrapper', () => ({ - ChannelSwipableWrapper: (...args: unknown[]) => mockChannelSwipableWrapper(...args), + ChannelSwipableWrapper: (...args: [React.PropsWithChildren]) => + mockChannelSwipableWrapper(...args), })); const ChannelPreviewUIComponent = (props: ChannelPreviewUIComponentProps) => { @@ -67,7 +68,7 @@ const initChannelFromData = async ( channel.initialized = true; channel.lastMessage = jest.fn().mockReturnValue(generateMessage()); channel.muteStatus = jest.fn().mockReturnValue({ muted: false }); - channel.state.messages = [generateMessage()]; + channel.state.messages = [generateMessage()] as unknown as typeof channel.state.messages; return channel; }; @@ -84,7 +85,13 @@ describe('ChannelPreview', () => { return ( - + + >, + }} + > @@ -308,7 +315,7 @@ describe('ChannelPreview', () => { const c = generateChannelResponse(); await useInitializeChannel(c); - channel.muteStatus = jest.fn().mockReturnValue({ muted: true }); + if (channel) channel.muteStatus = jest.fn().mockReturnValue({ muted: true }); const { getByTestId } = render(); @@ -362,7 +369,7 @@ describe('ChannelPreview', () => { }); await waitFor(() => { - expect(getByTestId('latest-message')).toHaveTextContent(message.text); + expect(getByTestId('latest-message')).toHaveTextContent(message.text as string); }); }); @@ -400,7 +407,9 @@ describe('ChannelPreview', () => { }, text: 'Hello world!', }; - const channel = generateChannelResponse({ messages: [message] }); + const channel = generateChannelResponse({ + messages: [message] as unknown as GetOrCreateChannelApiParams['messages'], + }); await useInitializeChannel(channel); const { getByText } = render(); @@ -435,10 +444,12 @@ describe('ChannelPreview', () => { return ( ['overrides'] + } > ({ rightActionsProbe.items = items; return null; }, - SwipableWrapper: (...args: unknown[]) => mockSwipableWrapper(...args), + SwipableWrapper: (...args: [React.PropsWithChildren>]) => + mockSwipableWrapper(...args), })); describe('ChannelSwipableWrapper', () => { diff --git a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx index 2d18faf99b..e818e72aaa 100644 --- a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx +++ b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx @@ -19,7 +19,7 @@ describe('useChannelPreviewDisplayPresence', () => { chatClient = await getTestClientWithUser({ id: currentUserId, userID: currentUserId, - }); + } as unknown as Parameters[0]); // Create mock channel mockChannel = { diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx index c674c70fa9..96e32e69ea 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx @@ -17,7 +17,8 @@ import { } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { ImageGalleryStateStore } from '../../../state-store/image-gallery-state-store'; -import { ImageGalleryGrid, ImageGalleryGridProps } from '../components/ImageGrid'; +import { ImageGalleryGrid } from '../components/ImageGrid'; +import type { ImageGalleryGridProps } from '../components/types'; const ImageGalleryGridComponent = ( props: Partial & { message: LocalMessage }, diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx index 5ef31d5557..c5755f6757 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx @@ -77,9 +77,12 @@ describe('ImageGalleryHeader', () => { const setOverlayMock = jest.fn(); const user = userEvent.setup(); - jest.spyOn(overlayContext, 'useOverlayContext').mockImplementation(() => ({ - setOverlay: setOverlayMock, - })); + jest.spyOn(overlayContext, 'useOverlayContext').mockImplementation( + () => + ({ + setOverlay: setOverlayMock, + }) as unknown as ReturnType, + ); render(); diff --git a/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx b/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx index 41bffa5fb1..42932b8ef3 100644 --- a/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx +++ b/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx @@ -19,11 +19,14 @@ const ImageGalleryComponentWrapper = ({ children }: PropsWithChildren) => { const initialImageGalleryStateStore = new ImageGalleryStateStore(); const attachment = generateImageAttachment(); initialImageGalleryStateStore.openImageGallery({ - message: generateMessage({ - attachments: [attachment], - user: {}, - }) as unknown as LocalMessage, - selectedAttachmentUrl: attachment.url, + messages: [ + generateMessage({ + attachments: [attachment], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + user: {} as any, + }) as unknown as LocalMessage, + ], + selectedAttachmentUrl: (attachment as unknown as { url?: string }).url, }); const [imageGalleryStateStore] = useState(initialImageGalleryStateStore); diff --git a/package/src/components/Message/hooks/__tests__/useShouldUseOverlayStyles.test.tsx b/package/src/components/Message/hooks/__tests__/useShouldUseOverlayStyles.test.tsx index a8173e45f9..87294c0f3e 100644 --- a/package/src/components/Message/hooks/__tests__/useShouldUseOverlayStyles.test.tsx +++ b/package/src/components/Message/hooks/__tests__/useShouldUseOverlayStyles.test.tsx @@ -106,7 +106,7 @@ describe('useShouldUseOverlayStyles', () => { const first = renderHook(() => useShouldUseOverlayStyles(), { wrapper: createWrapper( createMessageContextValue({ - message: sharedMessage, + message: sharedMessage as unknown as MessageContextValue['message'], messageOverlayId: 'message-overlay-first', }), ), @@ -115,7 +115,7 @@ describe('useShouldUseOverlayStyles', () => { const second = renderHook(() => useShouldUseOverlayStyles(), { wrapper: createWrapper( createMessageContextValue({ - message: sharedMessage, + message: sharedMessage as unknown as MessageContextValue['message'], messageOverlayId: 'message-overlay-second', }), ), diff --git a/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx b/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx index d5b9adf7b6..df959fc386 100644 --- a/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +++ b/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx @@ -1,14 +1,16 @@ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Attachment, Channel as ChannelType, LocalAttachment, StreamChat } from 'stream-chat'; + import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; import { - generateAudioAttachment, - generateFileAttachment, - generateImageAttachment, - generateVideoAttachment, + generateAudioAttachment as generateAudioAttachmentBase, + generateFileAttachment as generateFileAttachmentBase, + generateImageAttachment as generateImageAttachmentBase, + generateVideoAttachment as generateVideoAttachmentBase, } from '../../../mock-builders/attachments'; import { FileState } from '../../../utils/utils'; @@ -16,6 +18,15 @@ import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { AttachmentUploadPreviewList } from '../components/AttachmentPreview/AttachmentUploadPreviewList'; +const generateAudioAttachment = (a?: unknown): LocalAttachment => + generateAudioAttachmentBase(a as Partial) as unknown as LocalAttachment; +const generateFileAttachment = (a?: unknown): LocalAttachment => + generateFileAttachmentBase(a as Partial) as unknown as LocalAttachment; +const generateImageAttachment = (a?: unknown): LocalAttachment => + generateImageAttachmentBase(a as Partial) as unknown as LocalAttachment; +const generateVideoAttachment = (a?: unknown): LocalAttachment => + generateVideoAttachmentBase(a as Partial) as unknown as LocalAttachment; + jest.mock('../../../native.ts', () => { const { View } = require('react-native'); @@ -33,7 +44,15 @@ jest.mock('../../../native.ts', () => { }; }); -const renderComponent = ({ client, channel, props }) => { +const renderComponent = ({ + client, + channel, + props, +}: { + client: StreamChat; + channel: ChannelType; + props: Partial>; +}) => { return render( @@ -46,8 +65,8 @@ const renderComponent = ({ client, channel, props }) => { }; describe('AttachmentUploadPreviewList', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx index 8eaad78233..45569459a2 100644 --- a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx +++ b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx @@ -1,16 +1,21 @@ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Attachment, Channel as ChannelType, LocalAttachment, StreamChat } from 'stream-chat'; + import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; -import { generateAudioAttachment } from '../../../mock-builders/attachments'; +import { generateAudioAttachment as generateAudioAttachmentBase } from '../../../mock-builders/attachments'; import { FileState } from '../../../utils/utils'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { AttachmentUploadPreviewList } from '../components/AttachmentPreview/AttachmentUploadPreviewList'; +const generateAudioAttachment = (a?: unknown): LocalAttachment => + generateAudioAttachmentBase(a as Partial) as unknown as LocalAttachment; + jest.mock('../../../native.ts', () => { const View = require('react-native').View; @@ -28,7 +33,15 @@ jest.mock('../../../native.ts', () => { }; }); -const renderComponent = ({ client, channel, props }) => { +const renderComponent = ({ + client, + channel, + props, +}: { + client: StreamChat; + channel: ChannelType; + props: Partial>; +}) => { return render( @@ -41,8 +54,8 @@ const renderComponent = ({ client, channel, props }) => { }; describe('AudioAttachmentUploadPreview render', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx index 73127f2680..c8342610d0 100644 --- a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx +++ b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewExpo.test.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { act, fireEvent, render, screen } from '@testing-library/react-native'; +import type { LocalAudioAttachment } from 'stream-chat'; + import { MessageInputContext, MessageInputContextValue, @@ -23,9 +25,15 @@ jest.mock('../../../native.ts', () => ({ }, })); -const getComponent = ( - props: Partial>, -) => ( +type GetComponentProps = Omit, 'item'> & { + fileUploads?: unknown[]; + item?: unknown; + onLoad?: (...args: unknown[]) => unknown; + onPlayPause?: (...args: unknown[]) => unknown; + onProgress?: (...args: unknown[]) => unknown; +}; + +const getComponent = (props: GetComponentProps) => ( { const onPlayPauseMock = jest.fn(); render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], item: { file: { name: 'audio.mp3', uri: 'https://www.test.com/audio.mp3' }, paused: true, progress: 1, - } as unknown as FileUpload, + } as unknown as LocalAudioAttachment, onPlayPause: onPlayPauseMock, }), ); @@ -76,12 +88,16 @@ describe.skip('AudioAttachmentExpo', () => { const onPlayPauseMock = jest.fn(); render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], item: { file: { name: 'audio.mp3', uri: 'https://www.test.com/audio.mp3' }, paused: true, progress: 1, - } as unknown as FileUpload, + } as unknown as LocalAudioAttachment, onPlayPause: onPlayPauseMock, }), ); @@ -105,12 +121,16 @@ describe.skip('AudioAttachmentExpo', () => { const onPlayPauseMock = jest.fn(); render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], item: { file: { name: 'audio.mp3', uri: 'https://www.test.com/audio.mp3' }, paused: false, progress: 1, - } as unknown as FileUpload, + } as unknown as LocalAudioAttachment, onPlayPause: onPlayPauseMock, }), ); @@ -136,12 +156,16 @@ describe.skip('AudioAttachmentExpo', () => { const { unmount } = render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], item: { file: { name: 'audio.mp3', uri: 'https://www.test.com/audio.mp3' }, paused: false, progress: 1, - } as unknown as FileUpload, + } as unknown as LocalAudioAttachment, }), ); @@ -154,11 +178,15 @@ describe.skip('AudioAttachmentExpo', () => { it('render text in rtl mode', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], item: { file: { name: 'audio.mp3', uri: 'https://www.test.com/audio.mp3' }, progress: 1, - } as unknown as FileUpload, + } as unknown as LocalAudioAttachment, }), ); @@ -178,8 +206,12 @@ describe.skip('AudioAttachmentExpo', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: false } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { file: { name: 'audio.mp3' }, paused: false } as unknown as LocalAudioAttachment, onProgress: onProgressMock, }), ); diff --git a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx index 23b319fd1a..af4cb8ad94 100644 --- a/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx +++ b/package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreviewNative.test.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { act, fireEvent, render, screen } from '@testing-library/react-native'; +import type { LocalAudioAttachment } from 'stream-chat'; + import { MessageInputContext, MessageInputContextValue, @@ -23,9 +25,15 @@ jest.mock('../../../native.ts', () => { }; }); -const getComponent = ( - props: Partial>, -) => ( +type GetComponentProps = Omit, 'item'> & { + fileUploads?: unknown[]; + item?: unknown; + onLoad?: (...args: unknown[]) => unknown; + onPlayPause?: (...args: unknown[]) => unknown; + onProgress?: (...args: unknown[]) => unknown; +}; + +const getComponent = (props: GetComponentProps) => ( { const onPlayPauseMock = jest.fn(); render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: true, progress: 1 } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { + file: { name: 'audio.mp3' }, + paused: true, + progress: 1, + } as unknown as LocalAudioAttachment, onPlayPause: onPlayPauseMock, }), ); @@ -71,8 +87,12 @@ describe.skip('AudioAttachment', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: true } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { file: { name: 'audio.mp3' }, paused: true } as unknown as LocalAudioAttachment, onPlayPause: onPlayPauseMock, }), ); @@ -98,8 +118,12 @@ describe.skip('AudioAttachment', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: false } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { file: { name: 'audio.mp3' }, paused: false } as unknown as LocalAudioAttachment, onPlayPause: onPlayPauseMock, }), ); @@ -118,8 +142,12 @@ describe.skip('AudioAttachment', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: false } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { file: { name: 'audio.mp3' }, paused: false } as unknown as LocalAudioAttachment, onLoad: onLoadMock, }), ); @@ -141,8 +169,12 @@ describe.skip('AudioAttachment', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: false } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { file: { name: 'audio.mp3' }, paused: false } as unknown as LocalAudioAttachment, onPlayPause: onPlayPauseMock, onProgress: onProgressMock, }), @@ -163,8 +195,12 @@ describe.skip('AudioAttachment', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: false } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { file: { name: 'audio.mp3' }, paused: false } as unknown as LocalAudioAttachment, onProgress: onProgressMock, }), ); @@ -193,8 +229,12 @@ describe.skip('AudioAttachment', () => { render( getComponent({ - fileUploads: [generateFileUploadPreview({ type: 'audio/mp3' })], - item: { file: { name: 'audio.mp3' }, paused: false } as unknown as FileUpload, + fileUploads: [ + generateFileUploadPreview({ type: 'audio/mp3' } as unknown as Parameters< + typeof generateFileUploadPreview + >[0]), + ], + item: { file: { name: 'audio.mp3' }, paused: false } as unknown as LocalAudioAttachment, onProgress: onProgressMock, }), ); diff --git a/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx b/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx index ede84902ce..dfaaccaeaf 100644 --- a/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx +++ b/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx @@ -17,21 +17,19 @@ import { Chat } from '../../Chat/Chat'; import { MessageComposer } from '../MessageComposer'; jest.spyOn(Alert, 'alert'); -jest.spyOn(AttachmentPickerUtils, 'useAttachmentPickerContext').mockImplementation( - jest.fn(() => { - const attachmentPickerStore = new AttachmentPickerStore(); - attachmentPickerStore.setSelectedPicker('images'); - return { - AttachmentPickerSelectionBar, - AttachmentPickerContent, - closePicker: jest.fn(), - openPicker: jest.fn(), - setBottomInset: jest.fn(), - setTopInset: jest.fn(), - attachmentPickerStore, - }; - }), -); +jest.spyOn(AttachmentPickerUtils, 'useAttachmentPickerContext').mockImplementation(() => { + const attachmentPickerStore = new AttachmentPickerStore(); + attachmentPickerStore.setSelectedPicker('images'); + return { + AttachmentPickerSelectionBar, + AttachmentPickerContent, + closePicker: jest.fn(), + openPicker: jest.fn(), + setBottomInset: jest.fn(), + setTopInset: jest.fn(), + attachmentPickerStore, + } as unknown as ReturnType; +}); const renderComponent = ({ channelProps, client, props }) => { return render( diff --git a/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx b/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx index c1feb7d108..53a486b2b0 100644 --- a/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx +++ b/package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { Alert } from 'react-native'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { MessageComposer as StreamMessageComposer } from 'stream-chat'; import * as AttachmentPickerUtils from '../../../contexts/attachmentPickerContext/AttachmentPickerContext'; @@ -24,23 +25,29 @@ import { Chat } from '../../Chat/Chat'; import { MessageComposer } from '../MessageComposer'; jest.spyOn(Alert, 'alert'); -jest.spyOn(AttachmentPickerUtils, 'useAttachmentPickerContext').mockImplementation( - jest.fn(() => { - const attachmentPickerStore = new AttachmentPickerStore(); - attachmentPickerStore.setSelectedPicker('images'); - return { - AttachmentPickerSelectionBar, - AttachmentPickerContent, - closePicker: jest.fn(), - openPicker: jest.fn(), - setBottomInset: jest.fn(), - setTopInset: jest.fn(), - attachmentPickerStore, - }; - }), -); - -const renderComponent = ({ channelProps, client, props }) => { +jest.spyOn(AttachmentPickerUtils, 'useAttachmentPickerContext').mockImplementation(() => { + const attachmentPickerStore = new AttachmentPickerStore(); + attachmentPickerStore.setSelectedPicker('images'); + return { + AttachmentPickerSelectionBar, + AttachmentPickerContent, + closePicker: jest.fn(), + openPicker: jest.fn(), + setBottomInset: jest.fn(), + setTopInset: jest.fn(), + attachmentPickerStore, + } as unknown as ReturnType; +}); + +const renderComponent = ({ + channelProps, + client, + props, +}: { + channelProps: Partial> & { channel: ChannelType }; + client: StreamChat; + props: Partial>; +}) => { return render( @@ -52,14 +59,22 @@ const renderComponent = ({ channelProps, client, props }) => { ); }; -const editedMessageSetup = async ({ composerConfig, composition } = {}) => { +const editedMessageSetup = async ({ + composerConfig, + composition, +}: { + composerConfig?: ConstructorParameters[0]['config']; + composition?: ConstructorParameters[0]['composition']; +} = {}) => { const { client: chatClient, channels } = await initiateClientWithChannels(); const channel = channels[0]; const messageComposer = new StreamMessageComposer({ client: chatClient, composition, - compositionContext: composition, + compositionContext: composition as unknown as ConstructorParameters< + typeof StreamMessageComposer + >[0]['compositionContext'], config: composerConfig, }); @@ -70,8 +85,8 @@ const editedMessageSetup = async ({ composerConfig, composition } = {}) => { }; describe('SendMessageDisallowedIndicator', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); @@ -101,8 +116,8 @@ describe('SendMessageDisallowedIndicator', () => { act(() => { client.dispatchEvent({ - cid: channel.data.cid, - own_capabilities: channel.data.own_capabilities.filter( + cid: channel.data!.cid, + own_capabilities: channel.data!.own_capabilities!.filter( (capability) => capability !== 'send-message', ), type: 'capabilities.changed', @@ -139,11 +154,12 @@ describe('SendMessageDisallowedIndicator', () => { client.dispatchEvent({ channel: { ...channel.data, - own_capabilities: channel.data.own_capabilities.filter( - (capability) => capability !== 'send-message', + own_capabilities: channel.data!.own_capabilities!.filter( + (capability: string) => capability !== 'send-message', ), - }, - cid: channel.data.cid, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + cid: channel.data!.cid, type: 'channel.updated', }); }); @@ -180,9 +196,9 @@ describe("SendMessageDisallowedIndicator's edited state", () => { act(() => { chatClient.dispatchEvent({ - cid: customChannel.data.cid, - own_capabilities: customChannel.data.own_capabilities.filter( - (capability) => capability !== 'send-message', + cid: customChannel.data!.cid, + own_capabilities: customChannel.data!.own_capabilities!.filter( + (capability: string) => capability !== 'send-message', ), type: 'capabilities.changed', }); diff --git a/package/src/components/MessageList/__tests__/MessageSystem.test.tsx b/package/src/components/MessageList/__tests__/MessageSystem.test.tsx index d20d48f2d6..ab1bcebdf6 100644 --- a/package/src/components/MessageList/__tests__/MessageSystem.test.tsx +++ b/package/src/components/MessageList/__tests__/MessageSystem.test.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { LocalMessage } from 'stream-chat'; + import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { defaultTheme } from '../../../contexts/themeContext/utils/theme'; import { TranslationProvider } from '../../../contexts/translationContext/TranslationContext'; @@ -13,7 +15,9 @@ import { MessageSystem } from '../MessageSystem'; afterEach(cleanup); -let i18nInstance; +let i18nInstance: Streami18n; + +const toLocalMessage = (m: unknown): LocalMessage => m as LocalMessage; describe('MessageSystem', () => { beforeAll(() => { @@ -25,9 +29,13 @@ describe('MessageSystem', () => { const translators = await i18nInstance.getTranslators(); const message = generateMessage(); const { queryByTestId } = render( - - - + [0]['style']} + > + [0]['value']} + > + , ); @@ -42,9 +50,13 @@ describe('MessageSystem', () => { const user = generateStaticUser(0); const message = generateStaticMessage('Hello World', { user }); render( - - - + [0]['style']} + > + [0]['value']} + > + , ); diff --git a/package/src/components/MessageList/__tests__/TypingIndicator.test.tsx b/package/src/components/MessageList/__tests__/TypingIndicator.test.tsx index a3e0efad04..4d37e202de 100644 --- a/package/src/components/MessageList/__tests__/TypingIndicator.test.tsx +++ b/package/src/components/MessageList/__tests__/TypingIndicator.test.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { cleanup, render, waitFor } from '@testing-library/react-native'; +import type { Event, StreamChat } from 'stream-chat'; + import { TypingProvider } from '../../../contexts/typingContext/TypingContext'; import { generateStaticUser, generateUser } from '../../../mock-builders/generator/user'; @@ -12,7 +14,7 @@ import { TypingIndicator } from '../TypingIndicator'; afterEach(cleanup); describe('TypingIndicator', () => { - let chatClient; + let chatClient: StreamChat; it('should render typing indicator for two users', async () => { const user0 = generateUser(); @@ -25,7 +27,7 @@ describe('TypingIndicator', () => { const { getAllByTestId, getByTestId } = render( - + }}> , @@ -46,7 +48,7 @@ describe('TypingIndicator', () => { const { getAllByTestId, getByTestId } = render( - + }}> , @@ -68,7 +70,7 @@ describe('TypingIndicator', () => { const { toJSON } = render( - + }}> , diff --git a/package/src/components/MessageList/__tests__/useMessageList.test.tsx b/package/src/components/MessageList/__tests__/useMessageList.test.tsx index e9191a204d..2d0c0a3336 100644 --- a/package/src/components/MessageList/__tests__/useMessageList.test.tsx +++ b/package/src/components/MessageList/__tests__/useMessageList.test.tsx @@ -27,7 +27,7 @@ beforeEach(async () => { const messages = new Array(10) .fill(undefined) - .map((_: undefined, id: number) => generateMessage({ id })); + .map((_: undefined, id: number) => generateMessage({ id: String(id) })); const Providers: FC<{ children: React.ReactNode }> = ({ children }) => { const messageListContext = useCreatePaginatedMessageListContext({ @@ -57,7 +57,7 @@ describe('useMessageList', () => { useMessageList({ noGroupByUser: true, threadList: false, - }), + } as unknown as Parameters[0]), { wrapper: Providers }, ); const reversedMessages = messages.reverse(); diff --git a/package/src/components/MessageMenu/__tests__/MessageActionList.test.tsx b/package/src/components/MessageMenu/__tests__/MessageActionList.test.tsx index 4f3c178d35..33aebd3a21 100644 --- a/package/src/components/MessageMenu/__tests__/MessageActionList.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageActionList.test.tsx @@ -9,11 +9,14 @@ import { render } from '@testing-library/react-native'; import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { defaultTheme } from '../../../contexts/themeContext/utils/theme'; import { MessageActionList } from '../MessageActionList'; +import type { MessageActionListProps } from '../MessageActionList'; import { MessageActionListItemProps } from '../MessageActionListItem'; const MockMessageActionListItem = (props: MessageActionListItemProps) => {props.title}; -const defaultProps = { +const defaultProps: MessageActionListProps & { + MessageActionListItem: typeof MockMessageActionListItem; +} = { MessageActionListItem: MockMessageActionListItem, messageActions: [ { action: jest.fn(), actionType: 'copyMessage', type: 'standard', title: 'Copy Message' }, diff --git a/package/src/components/MessageMenu/__tests__/MessageActionListItem.test.tsx b/package/src/components/MessageMenu/__tests__/MessageActionListItem.test.tsx index ec5ac0f4b3..a3ece69894 100644 --- a/package/src/components/MessageMenu/__tests__/MessageActionListItem.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageActionListItem.test.tsx @@ -18,6 +18,7 @@ describe('MessageActionListItem', () => { actionType: 'copyMessage', icon: Icon, title: 'Copy Message', + type: 'standard' as const, }; it('should render correctly with given props', () => { diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx index dd8d675c0d..e5157e19a1 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React, { ComponentProps } from 'react'; import { render } from '@testing-library/react-native'; +import type { StreamChat } from 'stream-chat'; + import { defaultTheme } from '../../../contexts/themeContext/utils/theme'; import { getTestClientWithUser } from '../../../mock-builders/mock'; import { Chat } from '../../Chat/Chat'; @@ -9,7 +11,7 @@ import { MessageUserReactionsAvatar } from '../MessageUserReactionsAvatar'; describe('MessageUserReactionsAvatar', () => { const reaction = { id: 'test-user', image: 'image-url', name: 'Test User', type: 'like' }; // Mock reaction data - let chatClient; + let chatClient: StreamChat; beforeEach(async () => { chatClient = await getTestClientWithUser({ id: 'me' }); @@ -17,7 +19,10 @@ describe('MessageUserReactionsAvatar', () => { it('should render Avatar with correct image, name, and default size', () => { const { queryByTestId } = render( - + ['style']} + > , ); @@ -28,7 +33,10 @@ describe('MessageUserReactionsAvatar', () => { it('should render Avatar with correct image, name, and custom size', () => { const { queryByTestId } = render( - + ['style']} + > , ); diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactionsItem.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactionsItem.test.tsx index 1cfd026bcb..4074f877fb 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactionsItem.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactionsItem.test.tsx @@ -38,7 +38,8 @@ const renderComponent = async (props = {}, clientUserID = 'user2') => > ({ __esModule: true, - default: (...args: unknown[]) => mockReanimatedSwipeable(...args), + default: (...args: [React.PropsWithChildren]) => mockReanimatedSwipeable(...args), SwipeDirection: { LEFT: 'left', RIGHT: 'right', diff --git a/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx b/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx index 8baeed608e..f36fa82753 100644 --- a/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx @@ -32,8 +32,20 @@ const Wrapper = ({ channel, client, props }) => { } as ChannelContextValue } > - - + ['value'] + } + > + ['value'] + } + > { attachments: [generateLocalFileUploadAttachmentData()], cid: 'messaging:channel-id', text: 'test', - }) as LocalMessage; + }) as unknown as LocalMessage; const { result } = renderHook(() => useMessageInputContext(), { initialProps, diff --git a/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx b/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx index bf826461d9..4fd04096a8 100644 --- a/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx +++ b/package/src/contexts/overlayContext/__tests__/MessageOverlayHostLayer.test.tsx @@ -36,9 +36,9 @@ jest.mock('react-native-reanimated', () => { const { View } = require('react-native'); const useStableSharedValue = (init: unknown) => { - const ref = React.useRef<{ + const ref = React.useRef(null) as React.MutableRefObject<{ value: unknown; - }>(); + } | null>; if (!ref.current) { const value = { value: init }; diff --git a/package/src/hooks/__tests__/useTranslatedMessage.test.tsx b/package/src/hooks/__tests__/useTranslatedMessage.test.tsx index dd2004d3ca..80fd2bc91c 100644 --- a/package/src/hooks/__tests__/useTranslatedMessage.test.tsx +++ b/package/src/hooks/__tests__/useTranslatedMessage.test.tsx @@ -27,7 +27,7 @@ describe('useTranslatedMessage', () => { nl_text: 'Hallo wereld!', }, text: 'Hello world!', - } as MessageResponse; + } as unknown as MessageResponse; render( @@ -46,7 +46,7 @@ describe('useTranslatedMessage', () => { no_text: 'Hallo verden!', }, text: 'Hello world!', - } as MessageResponse; + } as unknown as MessageResponse; render( @@ -62,7 +62,7 @@ describe('useTranslatedMessage', () => { it("returns the original text if the message doesn't contain any translations", async () => { const message = { text: 'Hello world!', - } as MessageResponse; + } as unknown as MessageResponse; render(); @@ -78,7 +78,7 @@ describe('useTranslatedMessage', () => { no_text: 'Hallo verden!', }, text: 'Hello world!', - } as MessageResponse; + } as unknown as MessageResponse; /** * The reason for the as unknown as MessageOverlayContextValue is that the provider From 0d164b6bfa509861a55742ce6876c8ba707bd3bd Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 14:43:34 +0200 Subject: [PATCH 07/15] refactor(tests): annotate offline-support helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Type annotations for the two large integration-test helpers. No behavior changes. - offline-feature.tsx: narrow `_fiber` access via `as unknown as { _fiber: { pendingProps: { testID: string } } }` at 13 call sites; rename `userId` → `user_id` in `generateMessage` options (snake_case per SDK); type `ChannelSort` literals properly (`last_updated: 1`); narrow `tablesInDb`, `JSON.parse` args, and `new Date(...)` args from `unknown`; import `ChannelMemberResponse`, `ChannelSort`, `Event`, `MessageResponse`, `ReactionResponse`, `ChannelAPIResponse` as `type` imports. - optimistic-update.tsx: local wrapper cast `Channel as unknown as ComponentType<... & { initialValue?: string }>` to preserve the legacy `initialValue` prop the test passes; typed empty accumulators (`const newLatestReactions: ReactionResponse[]`); non-null assertion on `newReaction.user!.id`; cast `doUpdateMessageRequest` callbacks whose test-return shape doesn't include SDK-required `duration` etc. Latent test bugs preserved (passing undeclared fields at runtime that the SDK types don't include — `cid` on `ChannelMemberResponse` and `GeneratedChannelResponseCustomValues`, `last_read` instead of `last_read_at` on Event). Widened locally with `as unknown as T & { cid: string }` etc. rather than changing test data. These are follow-up items. Takes test:typecheck 69 → 0. Full test suite: 752 passed, 14 skipped; only the pre-existing SQLite-isolation flake in offline-support/index.test.ts fails. No regressions. --- .../offline-support/offline-feature.tsx | 152 +++++++++++++----- .../offline-support/optimistic-update.tsx | 119 ++++++++------ 2 files changed, 184 insertions(+), 87 deletions(-) diff --git a/package/src/__tests__/offline-support/offline-feature.tsx b/package/src/__tests__/offline-support/offline-feature.tsx index 2438e5d2ad..304ba5f0f9 100644 --- a/package/src/__tests__/offline-support/offline-feature.tsx +++ b/package/src/__tests__/offline-support/offline-feature.tsx @@ -5,6 +5,13 @@ import { Text, View } from 'react-native'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { + ChannelMemberResponse, + ChannelSort, + Event, + MessageResponse, + ReactionResponse, +} from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; import { ChannelList } from '../../components/ChannelList/ChannelList'; @@ -88,7 +95,7 @@ export const Generic = () => { await waitFor(() => expect(screen.getByTestId('test-child')).toBeTruthy()); await waitFor(async () => { - const tablesInDb = await BetterSqlite.getTables(); + const tablesInDb = (await BetterSqlite.getTables()) as Array<{ name: string }>; const tableNamesInDB = tablesInDb.map((table) => table.name); const tablesNamesInSchema = Object.keys(tables); @@ -109,7 +116,7 @@ export const Generic = () => { let allReactions; let allReads; const getRandomInt = (lower, upper) => Math.floor(lower + Math.random() * (upper - lower + 1)); - const createChannel = (messagesOverride) => { + const createChannel = (messagesOverride?: Partial[]) => { const id = uuidv4(); const cid = `messaging:${id}`; // always guarantee at least 2 members for ease of use; cases that need to test specific behaviour @@ -117,13 +124,19 @@ export const Generic = () => { const begin = getRandomInt(0, allUsers.length - 3); // begin shouldn't be the end of users.length const end = getRandomInt(begin + 2, allUsers.length - 1); const usersForMembers = allUsers.slice(begin, end); - const members = usersForMembers.map((user) => - generateMember({ - cid, - user, - }), + const members = usersForMembers.map( + (user) => + // `cid` is not part of `ChannelMemberResponse`, but tests rely on reading it back from + // the generated member objects — keep the runtime shape and widen the type. + ({ + ...generateMember({ user }), + cid, + }) as unknown as ChannelMemberResponse & { cid: string }, ); - members.push(generateMember({ cid, user: chatClient.user })); + members.push({ + ...generateMember({ user: chatClient.user }), + cid, + } as unknown as ChannelMemberResponse & { cid: string }); const messages = messagesOverride || @@ -149,7 +162,7 @@ export const Generic = () => { id, latest_reactions: reactions, user, - userId: user.id, + user_id: user.id, }); }); @@ -164,13 +177,18 @@ export const Generic = () => { allMembers.push(...members); allReads.push(...reads); + // `cid` is not part of `GeneratedChannelResponseCustomValues`, but tests rely on reading it + // back as a top-level field on the generated channel response — keep the runtime shape and + // widen the input type. return generateChannelResponse({ cid, id, members, messages, read: reads, - }); + } as unknown as Parameters[0]) as ReturnType< + typeof generateChannelResponse + > & { cid: string }; }; beforeEach(async () => { @@ -202,7 +220,7 @@ export const Generic = () => { foo: 'bar', type: 'messaging', }; - const sort = { last_updated: 1 }; + const sort: ChannelSort = { last_updated: 1 }; const renderComponent = () => render( @@ -215,12 +233,14 @@ export const Generic = () => { const expectCIDsOnUIToBeInDB = async (queryAllByLabelText) => { const channelIdsOnUI = queryAllByLabelText('list-item').map( - (node) => node._fiber.pendingProps.testID, + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber.pendingProps + .testID, ); await waitFor(async () => { const channelQueriesRows = await BetterSqlite.selectFromTable('channelQueries'); - const cidsInDB = JSON.parse(channelQueriesRows[0].cids); + const cidsInDB = JSON.parse(channelQueriesRows[0].cids as string); const filterSortQueryInDB = channelQueriesRows[0].id; const actualFilterSortQueryInDB = convertFilterSortToQuery({ filters, sort }); @@ -237,7 +257,9 @@ export const Generic = () => { const expectAllChannelsWithStateToBeInDB = async (queryAllByLabelText) => { const channelIdsOnUI = queryAllByLabelText('list-item').map( - (node) => node._fiber.pendingProps.testID, + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber.pendingProps + .testID, ); await waitFor(async () => { @@ -289,7 +311,7 @@ export const Generic = () => { await waitFor(() => expect(screen.getByTestId('test-child')).toBeTruthy()); - const tablesInDb = await BetterSqlite.getTables(); + const tablesInDb = (await BetterSqlite.getTables()) as Array<{ name: string }>; const tableNamesInDB = tablesInDb.map((table) => table.name); const tablesNamesInSchema = Object.keys(tables); @@ -520,7 +542,11 @@ export const Generic = () => { await waitFor(() => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(newChannel.channel.cid)).toBeTruthy(); }); @@ -571,7 +597,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(removedChannel.cid)).toBeFalsy(); await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -598,7 +628,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(removedChannel.cid)).toBeFalsy(); await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -625,7 +659,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(hiddenChannel.cid)).toBeFalsy(); await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -656,7 +694,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(hiddenChannel.cid)).toBeFalsy(); await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -678,7 +720,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(hiddenChannel.cid)).toBeFalsy(); await expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -713,7 +759,11 @@ export const Generic = () => { await waitFor(() => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(newChannel.channel.cid)).toBeTruthy(); }); @@ -744,7 +794,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy(); expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -786,7 +840,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy(); expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -827,7 +885,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy(); expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -862,7 +924,11 @@ export const Generic = () => { await waitFor(async () => { const channelIdsOnUI = screen .queryAllByLabelText('list-item') - .map((node) => node._fiber.pendingProps.testID); + .map( + (node) => + (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber + .pendingProps.testID, + ); expect(channelIdsOnUI.includes(channelToTruncate.cid)).toBeTruthy(); expectCIDsOnUIToBeInDB(screen.queryAllByLabelText); @@ -955,7 +1021,7 @@ export const Generic = () => { ...targetMessage, latest_reactions: [...targetMessage.latest_reactions], }; - const newLatestReactions = []; + const newLatestReactions: ReactionResponse[] = []; newReactions.forEach((newReaction) => { newLatestReactions.push(newReaction); @@ -980,7 +1046,7 @@ export const Generic = () => { !messageWithNewReactionBase.latest_reactions.some( (initialReaction) => initialReaction.type === newReaction.type && - initialReaction.user.id === newReaction.user.id, + initialReaction.user!.id === newReaction.user!.id, ), ).length; @@ -995,7 +1061,7 @@ export const Generic = () => { expect( matchingReactionsRows.filter( (reaction) => - reaction.type === newReaction.type && reaction.userId === newReaction.user.id, + reaction.type === newReaction.type && reaction.userId === newReaction.user!.id, ).length, ).toBe(1); }); @@ -1036,7 +1102,7 @@ export const Generic = () => { ...targetMessage, latest_reactions: [...targetMessage.latest_reactions], }; - const newLatestReactions = []; + const newLatestReactions: ReactionResponse[] = []; newReactions.forEach((newReaction) => { newLatestReactions.push(newReaction); @@ -1188,7 +1254,7 @@ export const Generic = () => { ...targetMessage, latest_reactions: [...targetMessage.latest_reactions], }; - const newLatestReactions = []; + const newLatestReactions: ReactionResponse[] = []; newReactions.forEach((newReaction) => { newLatestReactions.push(newReaction); @@ -1218,7 +1284,7 @@ export const Generic = () => { expect( matchingReactionsRows.filter( (reaction) => - reaction.type === newReaction.type && reaction.userId === newReaction.user.id, + reaction.type === newReaction.type && reaction.userId === newReaction.user!.id, ).length, ).toBe(1); }); @@ -1306,7 +1372,7 @@ export const Generic = () => { (m) => m.id === messageWithNewReaction.id, )[0]; - const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups); + const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups as string); expect(reactionGroups[newReaction.type]?.count).toBe(999); expect(reactionGroups[newReaction.type]?.sum_scores).toBe(999); @@ -1363,7 +1429,7 @@ export const Generic = () => { (m) => m.id === messageWithNewReaction.id, )[0]; - const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups); + const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups as string); expect(reactionGroups[newReaction.type]?.count).toBe(999); expect(reactionGroups[newReaction.type]?.sum_scores).toBe(999); @@ -1420,7 +1486,7 @@ export const Generic = () => { (m) => m.id === messageWithNewReaction.id, )[0]; - const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups); + const reactionGroups = JSON.parse(messageWithReactionRow.reactionGroups as string); expect(reactionGroups[newReaction.type]?.count).toBe(999); expect(reactionGroups[newReaction.type]?.sum_scores).toBe(999); @@ -1532,7 +1598,7 @@ export const Generic = () => { expect(matchingChannelsRows.length).toBe(1); - const extraData = JSON.parse(matchingChannelsRows[0].extraData); + const extraData = JSON.parse(matchingChannelsRows[0].extraData as string); expect(extraData.name).toBe(targetChannel.channel.name); }); @@ -1551,11 +1617,14 @@ export const Generic = () => { const readTimestamp = new Date().toISOString(); act(() => { + // `last_read` is not on `Event` (the real field is `last_read_at`), but the test fixture + // has historically passed `last_read`. Preserve the runtime payload shape exactly and + // widen the type at the call site. dispatchMessageReadEvent(chatClient, targetMember.user, targetChannel.channel, { first_unread_message_id: '123', last_read: readTimestamp, last_read_message_id: '321', - }); + } as unknown as Partial); }); await waitFor(async () => { @@ -1571,7 +1640,8 @@ export const Generic = () => { // expect(matchingReadRows[0].firstUnreadMessageId).toBe('123'); expect( Math.abs( - new Date(matchingReadRows[0].lastRead).getTime() - new Date(readTimestamp).getTime(), + new Date(matchingReadRows[0].lastRead as string).getTime() - + new Date(readTimestamp).getTime(), ), ).toBeLessThanOrEqual(1); }); @@ -1596,12 +1666,13 @@ export const Generic = () => { dispatchNotificationMarkUnread( chatClient, targetChannel.channel, + // `last_read` is not on `Event`; see note above. { first_unread_message_id: '123', last_read: readTimestamp, last_read_message_id: '321', unread_messages: 5, - }, + } as unknown as Partial, targetMember.user, ); }); @@ -1619,7 +1690,8 @@ export const Generic = () => { // expect(matchingReadRows[0].firstUnreadMessageId).toBe('123'); expect( Math.abs( - new Date(matchingReadRows[0].lastRead).getTime() - new Date(readTimestamp).getTime(), + new Date(matchingReadRows[0].lastRead as string).getTime() - + new Date(readTimestamp).getTime(), ), ).toBeLessThanOrEqual(1); }); diff --git a/package/src/__tests__/offline-support/optimistic-update.tsx b/package/src/__tests__/offline-support/optimistic-update.tsx index 04a74e2b67..a31218028d 100644 --- a/package/src/__tests__/offline-support/optimistic-update.tsx +++ b/package/src/__tests__/offline-support/optimistic-update.tsx @@ -3,9 +3,15 @@ import { View } from 'react-native'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { + ChannelAPIResponse, + ChannelMemberResponse, + MessageResponse, + ReactionResponse, +} from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; -import { Channel } from '../../components/Channel/Channel'; +import { Channel as ChannelRaw } from '../../components/Channel/Channel'; import { Chat } from '../../components/Chat/Chat'; import { MessageInputContext, MessagesContext } from '../../contexts'; import { deleteMessageApi } from '../../mock-builders/api/deleteMessage'; @@ -28,6 +34,13 @@ import { SqliteClient } from '../../store/SqliteClient'; import { BetterSqlite } from '../../test-utils/BetterSqlite'; import { MessageStatusTypes } from '../../utils/utils'; +// `initialValue` is not part of Channel's props today, but these legacy tests pass it to +// mimic a pre-populated input. Keep the runtime behavior unchanged and widen the prop type +// at the component boundary so TS stops complaining. +const Channel = ChannelRaw as unknown as React.ComponentType< + React.ComponentProps & { initialValue?: string } +>; + test('Workaround to allow exporting tests', () => expect(true).toBe(true)); export const OptimisticUpdates = () => { @@ -37,10 +50,14 @@ export const OptimisticUpdates = () => { const getRandomInt = (lower, upper) => Math.floor(lower + Math.random() * (upper - lower + 1)); const createChannel = () => { const allUsers = Array(20).fill(1).map(generateUser); - const allMessages = []; - const allMembers = []; - const allReactions = []; - const allReads = []; + const allMessages: MessageResponse[] = []; + const allMembers: ChannelMemberResponse[] = []; + const allReactions: ReactionResponse[] = []; + const allReads: Array<{ + last_read: Date; + unread_messages: number; + user: ReturnType | undefined; + }> = []; const id = uuidv4(); const cid = `messaging:${id}`; const begin = getRandomInt(0, allUsers.length - 2); // begin shouldn't be the end of users.length @@ -48,7 +65,6 @@ export const OptimisticUpdates = () => { const usersForMembers = allUsers.slice(begin, end); const members = usersForMembers.map((user) => generateMember({ - cid, user, }), ); @@ -74,7 +90,7 @@ export const OptimisticUpdates = () => { id, latest_reactions: reactions, user, - userId: user.id, + user_id: user.id, }); }); @@ -88,12 +104,17 @@ export const OptimisticUpdates = () => { allMembers.push(...members); allReads.push(...reads); + // `cid` is not part of `GeneratedChannelResponseCustomValues`, but tests rely on reading it + // back as a top-level field on the generated channel response — keep the runtime shape and + // widen the input type. return generateChannelResponse({ cid, id, members, messages, - }); + } as unknown as Parameters[0]) as ReturnType< + typeof generateChannelResponse + > & { cid: string; id: string }; }; beforeEach(async () => { @@ -112,7 +133,7 @@ export const OptimisticUpdates = () => { await SqliteClient.initializeDatabase(); await BetterSqlite.openDB(); await upsertChannels({ - channels: [channelResponse], + channels: [channelResponse] as unknown as ChannelAPIResponse[], isLatestMessagesSet: true, }); chatClient.wsConnection = { isHealthy: true, onlineStatusChanged: jest.fn() }; @@ -175,7 +196,7 @@ export const OptimisticUpdates = () => { await waitFor(async () => { const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks'); const pendingTaskType = pendingTasksRows?.[0]?.type; - const pendingTaskPayload = JSON.parse(pendingTasksRows?.[0]?.payload || '{}'); + const pendingTaskPayload = JSON.parse((pendingTasksRows?.[0]?.payload as string) || '{}'); expect(pendingTaskType).toBe('delete-message'); expect(pendingTaskPayload[0]).toBe(message.id); }); @@ -235,7 +256,7 @@ export const OptimisticUpdates = () => { await waitFor(async () => { const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks'); const pendingTaskType = pendingTasksRows?.[0]?.type; - const pendingTaskPayload = JSON.parse(pendingTasksRows?.[0]?.payload || '{}'); + const pendingTaskPayload = JSON.parse((pendingTasksRows?.[0]?.payload as string) || '{}'); expect(pendingTaskType).toBe('send-reaction'); expect(pendingTaskPayload[0]).toBe(targetMessage.id); }); @@ -301,7 +322,7 @@ export const OptimisticUpdates = () => { await waitFor(async () => { const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks'); const pendingTaskType = pendingTasksRows?.[0]?.type; - const pendingTaskPayload = JSON.parse(pendingTasksRows?.[0]?.payload || '{}'); + const pendingTaskPayload = JSON.parse((pendingTasksRows?.[0]?.payload as string) || '{}'); expect(pendingTaskType).toBe('send-message'); expect(pendingTaskPayload[0].id).toEqual(newMessage.id); expect(pendingTaskPayload[0].text).toEqual(newMessage.text); @@ -365,7 +386,7 @@ export const OptimisticUpdates = () => { await waitFor(async () => { const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks'); const pendingTaskType = pendingTasksRows?.[0]?.type; - const pendingTaskPayload = JSON.parse(pendingTasksRows?.[0]?.payload || '{}'); + const pendingTaskPayload = JSON.parse((pendingTasksRows?.[0]?.payload as string) || '{}'); expect(pendingTaskType).toBe('delete-reaction'); expect(pendingTaskPayload[0]).toBe(targetMessage.id); }); @@ -408,22 +429,24 @@ export const OptimisticUpdates = () => { { - await chatClient.offlineDb.addPendingTask({ - channelId: channel.id, - channelType: channel.type, - messageId: message.id, - payload: [localMessage, undefined, options], - type: 'update-message', - }); - return { - message: { - ...localMessage, - message_text_updated_at: new Date(), - updated_at: new Date(), - }, - }; - }} + doUpdateMessageRequest={ + (async (_channelId, localMessage, options) => { + await chatClient.offlineDb.addPendingTask({ + channelId: channel.id, + channelType: channel.type, + messageId: message.id, + payload: [localMessage, undefined, options], + type: 'update-message', + }); + return { + message: { + ...localMessage, + message_text_updated_at: new Date(), + updated_at: new Date(), + }, + }; + }) as unknown as React.ComponentProps['doUpdateMessageRequest'] + } > { @@ -456,8 +479,8 @@ export const OptimisticUpdates = () => { expect(updatedMessage.message_text_updated_at).toBeTruthy(); expect(pendingTasksRows).toHaveLength(1); expect(pendingTasksRows[0].type).toBe('update-message'); - expect(dbMessage.text).toBe(editedText); - expect(dbMessage.messageTextUpdatedAt).toBeTruthy(); + expect(dbMessage!.text).toBe(editedText); + expect(dbMessage!.messageTextUpdatedAt).toBeTruthy(); }); }); @@ -506,7 +529,7 @@ export const OptimisticUpdates = () => { expect(updatedMessage.text).toBe(editedText); expect(pendingTasksRows).toHaveLength(0); - expect(dbMessage.text).toBe(editedText); + expect(dbMessage!.text).toBe(editedText); }); }); @@ -518,16 +541,18 @@ export const OptimisticUpdates = () => { { - const optimisticMessage = channel.state.findMessage(message.id); - optimisticStateSpy(optimisticMessage); - - return { - message: { - ...optimisticMessage, - }, - }; - }} + doUpdateMessageRequest={ + (() => { + const optimisticMessage = channel.state.findMessage(message.id); + optimisticStateSpy(optimisticMessage); + + return { + message: { + ...optimisticMessage, + }, + }; + }) as unknown as React.ComponentProps['doUpdateMessageRequest'] + } > { @@ -611,12 +636,12 @@ export const OptimisticUpdates = () => { const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks'); const dbMessages = await BetterSqlite.selectFromTable('messages'); const dbMessage = dbMessages.find((row) => row.id === message.id); - const storedAttachments = JSON.parse(dbMessage.attachments); + const storedAttachments = JSON.parse(dbMessage!.attachments as string); expect(updatedMessage.text).toBe(editedText); expect(updatedMessage.attachments[0].asset_url).toBe(localUri); expect(pendingTasksRows).toHaveLength(0); - expect(dbMessage.text).toBe(editedText); + expect(dbMessage!.text).toBe(editedText); expect(storedAttachments[0].asset_url).toBe(localUri); }); }); @@ -726,13 +751,13 @@ export const OptimisticUpdates = () => { status: MessageStatusTypes.SENDING, text: 'offline resend', user: chatClient.user, - userId: chatClient.userID, + user_id: chatClient.userID, }); const serverMessage = generateMessage({ id: localMessage.id, text: localMessage.text, user: chatClient.user, - userId: chatClient.userID, + user_id: chatClient.userID, }); jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({ @@ -793,7 +818,7 @@ export const OptimisticUpdates = () => { status: MessageStatusTypes.SENDING, text: 'offline resend unresolved', user: chatClient.user, - userId: chatClient.userID, + user_id: chatClient.userID, }); jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({ From 95e137f516b516fdf7a5a6cc3b5726be6a9e7c0c Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 14:50:30 +0200 Subject: [PATCH 08/15] ci: wire yarn test:typecheck into PR checks Adds a `Typecheck tests` step to check-pr.yml that runs `yarn test:typecheck` (tsc --noEmit -p tsconfig.test.json) in the package/ workspace between lint and test. Tests and mock-builders are now type-gated at PR time. Leaves `noImplicitAny: false` in tsconfig.test.json with a TODO comment. Flipping it exposes ~630 mechanical implicit-any errors (destructured-parameter and `let`-variable annotations across ~60 test files). Filing as separate follow-up rather than ballooning this PR further. Full error count at this commit: 0. --- .github/workflows/check-pr.yml | 2 ++ package/tsconfig.test.json | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index f63dff717e..370cd0fd2f 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -29,5 +29,7 @@ jobs: uses: ./.github/actions/install-and-build-sdk - name: Lint run: yarn lerna-workspaces run lint + - name: Typecheck tests + run: cd package && yarn test:typecheck - name: Test run: yarn test:coverage diff --git a/package/tsconfig.test.json b/package/tsconfig.test.json index 8dae735147..ced9a158c2 100644 --- a/package/tsconfig.test.json +++ b/package/tsconfig.test.json @@ -3,6 +3,9 @@ "compilerOptions": { "noUnusedLocals": false, "noUnusedParameters": false, + // TODO: flip to `true` after annotating the ~630 remaining implicit-any + // errors in test files (mostly destructured-parameter and `let`-variable + // annotations). See follow-up issue. "noImplicitAny": false }, "include": ["./src/**/*"], From 1466189c95dcb816909af812194087efa62abc6d Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 14:55:10 +0200 Subject: [PATCH 09/15] style: prettier + ts-ignore fix for renderText.tsx build - src/components/Message/MessageItemView/utils/renderText.tsx: restore the directive for the untyped `react-native-markdown-package` import, but use `@ts-ignore` instead of `@ts-expect-error`. The base tsconfig (with noImplicitAny: true) needs the suppression for bob's `yarn build` to succeed; the test tsconfig (with noImplicitAny: false) doesn't report the import as an error, so `@ts-expect-error` would read as "unused". `@ts-ignore` is tolerant of both. - prettier auto-formatting across a handful of recently edited test files. --- .../Channel/__tests__/Channel.test.tsx | 12 +++--------- .../useMessageListPagination.test.tsx | 18 +++++++----------- .../__tests__/ChannelList.test.tsx | 8 ++------ .../__tests__/ChannelPreviewView.test.tsx | 4 +++- .../components/Chat/__tests__/Chat.test.tsx | 6 ++++-- .../__tests__/MessageContent.test.tsx | 5 +---- .../MessageItemView/utils/renderText.tsx | 1 + .../__tests__/ScrollToBottomButton.test.tsx | 4 +--- .../Thread/__tests__/Thread.test.tsx | 4 +--- .../src/utils/__tests__/Streami18n.test.ts | 19 +++++++++++++++---- 10 files changed, 38 insertions(+), 43 deletions(-) diff --git a/package/src/components/Channel/__tests__/Channel.test.tsx b/package/src/components/Channel/__tests__/Channel.test.tsx index 9af8d839cb..1707baf1b5 100644 --- a/package/src/components/Channel/__tests__/Channel.test.tsx +++ b/package/src/components/Channel/__tests__/Channel.test.tsx @@ -219,9 +219,7 @@ describe('Channel', () => { await waitFor(() => expect(hasThread).toHaveBeenCalledWith(threadMessage.id)); }); - const queryChannelWithNewMessages = ( - newMessages: ReturnType[], - ) => + const queryChannelWithNewMessages = (newMessages: ReturnType[]) => // generate new channel mock from existing channel with new messages added getOrCreateChannelApi( generateChannelResponse({ @@ -229,9 +227,7 @@ describe('Channel', () => { config: channel.getConfig(), id: channel.id, type: channel.type, - } as unknown as NonNullable< - Parameters[0] - >['channel'], + } as unknown as NonNullable[0]>['channel'], messages: newMessages, }), ); @@ -387,9 +383,7 @@ describe('Channel initial load useEffect', () => { const renderComponent = (props: RenderComponentProps = {}) => render( - )}> - {props.children} - + )}>{props.children} , ); diff --git a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx index 646684352f..13b720b91d 100644 --- a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx +++ b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx @@ -403,9 +403,7 @@ describe('useMessageListPagination', () => { }; initialMessages: MessageResponse[]; name: string; - setupLoadMessageIntoState: - | ((channel: ChannelType) => jest.Mock) - | null; + setupLoadMessageIntoState: ((channel: ChannelType) => jest.Mock) | null; }; // Test cases with different scenarios @@ -448,9 +446,8 @@ describe('useMessageListPagination', () => { channel.state.messages = newMessages as unknown as typeof channel.state.messages; (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); - ( - channel.state as unknown as { loadMessageIntoState: jest.Mock } - ).loadMessageIntoState = loadMessageIntoState; + (channel.state as unknown as { loadMessageIntoState: jest.Mock }).loadMessageIntoState = + loadMessageIntoState; return loadMessageIntoState; }, }, @@ -492,9 +489,8 @@ describe('useMessageListPagination', () => { channel.state.messages = newMessages as unknown as typeof channel.state.messages; (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); - ( - channel.state as unknown as { loadMessageIntoState: jest.Mock } - ).loadMessageIntoState = loadMessageIntoState; + (channel.state as unknown as { loadMessageIntoState: jest.Mock }).loadMessageIntoState = + loadMessageIntoState; return loadMessageIntoState; }, }, @@ -582,9 +578,9 @@ describe('useMessageListPagination', () => { const user = generateUser(); it.each` - scenario | last_read | expectedQueryCalls | expectedJumpToMessageFinishedCalls | expectedSetChannelUnreadStateCalls | expectedSetTargetedMessageCalls | expectedTargetedMessageId + scenario | last_read | expectedQueryCalls | expectedJumpToMessageFinishedCalls | expectedSetChannelUnreadStateCalls | expectedSetTargetedMessageCalls | expectedTargetedMessageId ${'when last_read matches a message'} | ${new Date(messages[10].created_at as unknown as Date)} | ${0} | ${1} | ${1} | ${1} | ${'10'} - ${'when last_read does not match any message'} | ${new Date('2021-09-02T00:00:00.000Z')} | ${1} | ${0} | ${0} | ${0} | ${undefined} + ${'when last_read does not match any message'} | ${new Date('2021-09-02T00:00:00.000Z')} | ${1} | ${0} | ${0} | ${0} | ${undefined} `( '$scenario', async ({ diff --git a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx index 04f2d71279..933e33ddc5 100644 --- a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx +++ b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx @@ -227,9 +227,7 @@ describe('ChannelList', () => { ['filters'] - } + filters={staleFilter as unknown as React.ComponentProps['filters']} /> , @@ -252,9 +250,7 @@ describe('ChannelList', () => { ['filters'] - } + filters={freshFilter as unknown as React.ComponentProps['filters']} /> , diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx index 908755696e..0b14822082 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx @@ -66,7 +66,9 @@ describe('ChannelPreviewView', () => { render( getComponent({ onSelect, - ...({ watchers: {} } as unknown as Partial>), + ...({ watchers: {} } as unknown as Partial< + React.ComponentProps + >), }), ); diff --git a/package/src/components/Chat/__tests__/Chat.test.tsx b/package/src/components/Chat/__tests__/Chat.test.tsx index f814b28f39..83cc845365 100644 --- a/package/src/components/Chat/__tests__/Chat.test.tsx +++ b/package/src/components/Chat/__tests__/Chat.test.tsx @@ -163,7 +163,8 @@ describe('TranslationContext', () => { const { t, tDateTimeParser } = await i18nInstance.getTranslators(); i18nInstance.t = (() => 't') as unknown as typeof i18nInstance.t; - i18nInstance.tDateTimeParser = (() => 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; + i18nInstance.tDateTimeParser = (() => + 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; render( @@ -188,7 +189,8 @@ describe('TranslationContext', () => { const i18nInstance = new Streami18n(); i18nInstance.t = (() => 't') as unknown as typeof i18nInstance.t; - i18nInstance.tDateTimeParser = (() => 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; + i18nInstance.tDateTimeParser = (() => + 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; const { rerender } = render( diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx index acbd146bc0..2b24dc7a4b 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx @@ -264,10 +264,7 @@ describe('MessageContent', () => { }} > - + diff --git a/package/src/components/Message/MessageItemView/utils/renderText.tsx b/package/src/components/Message/MessageItemView/utils/renderText.tsx index 7dc5f1dfa0..feb3b39f6f 100644 --- a/package/src/components/Message/MessageItemView/utils/renderText.tsx +++ b/package/src/components/Message/MessageItemView/utils/renderText.tsx @@ -11,6 +11,7 @@ import { } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; +// @ts-ignore -- no type definitions available for `react-native-markdown-package` import Markdown from 'react-native-markdown-package'; import Animated, { clamp, scrollTo, useAnimatedRef, useSharedValue } from 'react-native-reanimated'; diff --git a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx index 0bea239576..3d38f99dad 100644 --- a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx +++ b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx @@ -69,9 +69,7 @@ describe('ScrollToBottomButton', () => { const translators = await i18nInstance.getTranslators(); const { getByTestId, getByText } = render( - + )} onPress={() => null} diff --git a/package/src/components/Thread/__tests__/Thread.test.tsx b/package/src/components/Thread/__tests__/Thread.test.tsx index 8bd1065c76..5bd513b8cf 100644 --- a/package/src/components/Thread/__tests__/Thread.test.tsx +++ b/package/src/components/Thread/__tests__/Thread.test.tsx @@ -156,9 +156,7 @@ describe('Thread', () => { } > ['value'] - } + value={{} as unknown as React.ComponentProps['value']} > { dayjsLocaleConfigForLanguage: customDayjsLocaleConfig, language: 'nl', }; - const streami18n = new Streami18n(streami18nOptions as unknown as ConstructorParameters[0]); + const streami18n = new Streami18n( + streami18nOptions as unknown as ConstructorParameters[0], + ); it('should provide dayjs with given custom locale config', async () => { const { tDateTimeParser } = await streami18n.getTranslators(); expect(tDateTimeParser() instanceof Dayjs).toBe(true); - const localeConfig = (tDateTimeParser() as Dayjs.Dayjs).localeData() as unknown as Record; + const localeConfig = (tDateTimeParser() as Dayjs.Dayjs).localeData() as unknown as Record< + string, + unknown + >; for (const key in streami18nOptions.dayjsLocaleConfigForLanguage) { if (typeof localeConfig[key] === 'function') { expect((localeConfig[key] as () => unknown)()).toStrictEqual( @@ -268,7 +273,10 @@ describe('Streami18n timezone', () => { it('allows to override the default timestampFormatter', async () => { const i18n = new Streami18n({ formatters: { timestampFormatter: () => () => 'custom' }, - translationsForLanguage: { abc: '{{ value | timestampFormatter }}' } as unknown as Record, + translationsForLanguage: { abc: '{{ value | timestampFormatter }}' } as unknown as Record< + string, + string + >, }); await (i18n as unknown as { init: () => Promise }).init(); expect(i18n.t('abc')).toBe('custom'); @@ -276,7 +284,10 @@ describe('Streami18n timezone', () => { it('allows to add new custom formatter', async () => { const i18n = new Streami18n({ formatters: { customFormatter: () => () => 'custom' }, - translationsForLanguage: { abc: '{{ value | customFormatter }}' } as unknown as Record, + translationsForLanguage: { abc: '{{ value | customFormatter }}' } as unknown as Record< + string, + string + >, }); await (i18n as unknown as { init: () => Promise }).init(); expect(i18n.t('abc')).toBe('custom'); From 7bd650db74c2a675450332bb2e5818d72f576b31 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 15:48:34 +0200 Subject: [PATCH 10/15] refactor(mock-builders,tests): return LocalMessage from generateMessage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mock-builder `generateMessage` produces `LocalMessage`-shaped data at runtime (Date objects for `created_at`/`updated_at`/`pinned_at`, `deleted_at: null`), so typing the return as `MessageResponse` was a lie that forced test files to paper over it with `as unknown as LocalMessage` everywhere. - Mock-builders now return `LocalMessage`: - generator/message: returns `LocalMessage`, adds `deleted_at: null`, `pinned_at: null`, `status: 'received'`; uses Date objects for the date fields (matches `LocalMessage` type); `message_text_ updated_at` stays ISO string (still on `MessageResponseBase`). - API mocks and event dispatchers accept `MessageResponse | LocalMessage`: - api/{sendMessage,deleteMessage,sendReaction,deleteReaction, threadReplies,getOrCreateChannel}.ts - event/{messageNew,messageDeleted,messageUpdated,reactionNew, reactionDeleted,reactionUpdated}.ts (cast to `MessageResponse` at the Event payload boundary — that's where the wire-shape mismatch actually happens). - generator/channel: `GeneratedChannelResponseCustomValues.messages` widened to accept both. - Test files cleaned up: - Removed 33 `as unknown as LocalMessage` / `as unknown as LocalMessage[]` casts. The message value flows through without rewrapping. - Removed `toLocalMessage` / `toLocalMessages` wrapper helpers (57 call sites across 3 files) — no longer needed. - Replaced 13 `{...({...} as unknown as ComponentProps)}` spread+cast patterns with direct prop passing. The spread was hiding props that weren't actually accepted by the target component (dead props from pre-migration JS). Dead props removed (all verified by reading each component's prop type): - `MessageAuthor`: `alignment`, `groupStyles` (live on `MessageContextValue` but `MessageAuthor` doesn't pick them). - `MessageReplies`: `groupStyles`, `MessageRepliesAvatars` (component override, comes via `ComponentsContext`), `openThread` (typo — actual prop is `onOpenThread`). - `Message`: `reactionsEnabled`, `MessageFooter` (override, goes through `WithComponents`). - `ScrollToBottomButton`: `t` (supplied via `TranslationProvider`). - `ChannelPreviewView`: `client`, `latestMessagePreview`, `watchers`, `latestMessage`, `latestMessageLength` (none are real props). - `Giphy` test helper: tightened `props: Record` to `props: ComponentProps` so callers are type-checked. Other fixups: - `ownCapabilities.test.tsx`: helper arg `targetMessage: MessageResponse` -> `LocalMessage`. - `useMessageListPagination.test.tsx`: `initialMessages` and `channelUnreadState` / `targetedMessageId` callback args retyped to `LocalMessage[]`. - `optimistic-update.tsx`: `allMessages: LocalMessage[]`. - `isAttachmentEqualHandler.test.tsx`: drop stale `as unknown as string` on `updated_at: new Date()`. - `useMessageListPagination.test.tsx`: drop stale `as unknown as string` on `created_at`. - Thread.test.tsx snapshot updated: missing optional `quoted_message: null` / `reaction_groups: null` keys were artifacts of the SDK's old `formatMessage` normalization. The current `LocalMessage` type declares them optional; same runtime data. test:typecheck: 0 errors. Full suite: same pre-existing SQLite flake, no regressions. --- .../offline-support/optimistic-update.tsx | 4 +- .../Attachment/__tests__/Attachment.test.tsx | 3 +- .../Attachment/__tests__/Giphy.test.tsx | 13 +- .../isAttachmentEqualHandler.test.tsx | 2 +- .../__tests__/ownCapabilities.test.tsx | 4 +- .../useMessageListPagination.test.tsx | 10 +- .../__tests__/ChannelPreviewView.test.tsx | 38 +---- .../__tests__/ImageGallery.test.tsx | 24 ++-- .../__tests__/ImageGalleryFooter.test.tsx | 6 +- .../__tests__/ImageGalleryGrid.test.tsx | 6 +- .../__tests__/ImageGalleryHeader.test.tsx | 4 +- .../__tests__/ImageGalleryHeader.test.tsx | 4 +- .../__tests__/MessageAuthor.test.tsx | 27 +--- .../__tests__/MessageContent.test.tsx | 44 ++---- .../__tests__/MessagePinnedHeader.test.tsx | 7 +- .../__tests__/MessageReplies.test.tsx | 41 +----- .../__tests__/MessageStatus.test.tsx | 8 +- .../__tests__/MessageTextContainer.test.tsx | 8 +- .../__tests__/MessageSystem.test.tsx | 8 +- .../__tests__/ScrollToBottomButton.test.tsx | 7 +- .../__tests__/MessageUserReactions.test.tsx | 4 +- .../Thread/__tests__/Thread.test.tsx | 11 +- .../__snapshots__/Thread.test.tsx.snap | 21 --- .../__tests__/sendMessage.test.tsx | 4 +- .../src/mock-builders/api/deleteMessage.ts | 4 +- .../src/mock-builders/api/deleteReaction.ts | 4 +- .../mock-builders/api/getOrCreateChannel.ts | 10 +- package/src/mock-builders/api/sendMessage.ts | 10 +- package/src/mock-builders/api/sendReaction.ts | 4 +- .../src/mock-builders/api/threadReplies.ts | 6 +- .../src/mock-builders/event/messageDeleted.ts | 12 +- package/src/mock-builders/event/messageNew.ts | 12 +- .../src/mock-builders/event/messageUpdated.ts | 12 +- .../mock-builders/event/reactionDeleted.ts | 5 +- .../src/mock-builders/event/reactionNew.ts | 5 +- .../mock-builders/event/reactionUpdated.ts | 5 +- .../src/mock-builders/generator/channel.ts | 5 +- .../src/mock-builders/generator/message.ts | 40 +++--- .../image-gallery-state-store.test.ts | 130 ++++++++---------- 39 files changed, 227 insertions(+), 345 deletions(-) diff --git a/package/src/__tests__/offline-support/optimistic-update.tsx b/package/src/__tests__/offline-support/optimistic-update.tsx index a31218028d..fcb89de2d3 100644 --- a/package/src/__tests__/offline-support/optimistic-update.tsx +++ b/package/src/__tests__/offline-support/optimistic-update.tsx @@ -6,7 +6,7 @@ import { act, cleanup, render, screen, waitFor } from '@testing-library/react-na import type { ChannelAPIResponse, ChannelMemberResponse, - MessageResponse, + LocalMessage, ReactionResponse, } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; @@ -50,7 +50,7 @@ export const OptimisticUpdates = () => { const getRandomInt = (lower, upper) => Math.floor(lower + Math.random() * (upper - lower + 1)); const createChannel = () => { const allUsers = Array(20).fill(1).map(generateUser); - const allMessages: MessageResponse[] = []; + const allMessages: LocalMessage[] = []; const allMembers: ChannelMemberResponse[] = []; const allReactions: ReactionResponse[] = []; const allReads: Array<{ diff --git a/package/src/components/Attachment/__tests__/Attachment.test.tsx b/package/src/components/Attachment/__tests__/Attachment.test.tsx index 9d6df753b2..2f68ffa95c 100644 --- a/package/src/components/Attachment/__tests__/Attachment.test.tsx +++ b/package/src/components/Attachment/__tests__/Attachment.test.tsx @@ -1,7 +1,6 @@ import React, { ComponentProps } from 'react'; import { render, waitFor } from '@testing-library/react-native'; -import type { LocalMessage } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; @@ -28,7 +27,7 @@ jest.mock('../../../native.ts', () => ({ })); const getAttachmentComponent = (props: ComponentProps) => { - const message = generateMessage() as unknown as LocalMessage; + const message = generateMessage(); return ( { const lightTheme = mergeThemes({ scheme: 'light' }); const getAttachmentComponent = ( - props: Record, + props: ComponentProps, messageContextValue: Partial = {}, ) => { - const message = generateMessage() as unknown as LocalMessage; + const message = generateMessage(); return ( { - )} /> + diff --git a/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx b/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx index 78a11141f1..095e653447 100644 --- a/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx +++ b/package/src/components/Channel/__tests__/isAttachmentEqualHandler.test.tsx @@ -110,7 +110,7 @@ describe('isAttachmentEqualHandler', () => { attachments: [ { customField: 'custom-field-2', type: 'test' } as AttachmentWithCustomField, ], - updated_at: new Date() as unknown as string, + updated_at: new Date(), }, channel, ); diff --git a/package/src/components/Channel/__tests__/ownCapabilities.test.tsx b/package/src/components/Channel/__tests__/ownCapabilities.test.tsx index b81ebf271f..d8a9f012be 100644 --- a/package/src/components/Channel/__tests__/ownCapabilities.test.tsx +++ b/package/src/components/Channel/__tests__/ownCapabilities.test.tsx @@ -4,7 +4,7 @@ import { FlatList } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { act, fireEvent, render, waitFor } from '@testing-library/react-native'; -import type { Channel as ChannelType, MessageResponse, StreamChat } from 'stream-chat'; +import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; import { allOwnCapabilities } from '../../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext'; @@ -73,7 +73,7 @@ describe('Own capabilities', () => { }; const renderChannelAndOpenMessageActionsList = async ( - targetMessage: MessageResponse, + targetMessage: LocalMessage, props: Partial> = {}, ) => { const { findByTestId, queryByLabelText, queryByText, unmount } = render(getComponent(props)); diff --git a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx index 13b720b91d..7d222e76c6 100644 --- a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx +++ b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx @@ -1,5 +1,5 @@ import { act, cleanup, renderHook, waitFor } from '@testing-library/react-native'; -import type { Channel as ChannelType, MessageResponse, StreamChat } from 'stream-chat'; +import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; import { useMockedApis } from '../../../mock-builders/api/useMockedApis'; @@ -393,15 +393,15 @@ describe('useMessageListPagination', () => { }; type TestCase = { - channelUnreadState: (messages: MessageResponse[]) => TestCaseUnreadState; + channelUnreadState: (messages: LocalMessage[]) => TestCaseUnreadState; expectedCalls: { jumpToMessageFinishedCalls: number; loadMessageIntoStateCalls: number; setChannelUnreadStateCalls: number; setTargetedMessageIdCalls: number; - targetedMessageId: (messages: MessageResponse[]) => string; + targetedMessageId: (messages: LocalMessage[]) => string; }; - initialMessages: MessageResponse[]; + initialMessages: LocalMessage[]; name: string; setupLoadMessageIntoState: ((channel: ChannelType) => jest.Mock) | null; }; @@ -569,7 +569,7 @@ describe('useMessageListPagination', () => { const messages = Array.from({ length: 20 }, (_, i) => generateMessage({ - created_at: new Date('2021-09-01T00:00:00.000Z') as unknown as string, + created_at: new Date('2021-09-01T00:00:00.000Z'), id: String(i), text: `message-${i}`, }), diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx index 0b14822082..8eb02b765e 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx @@ -21,25 +21,7 @@ describe('ChannelPreviewView', () => { const getComponent = (props: Partial> = {}) => ( - )} - {...props} - /> + ); @@ -63,14 +45,7 @@ describe('ChannelPreviewView', () => { const onSelect = jest.fn(); await initializeChannel(generateChannelResponse()); - render( - getComponent({ - onSelect, - ...({ watchers: {} } as unknown as Partial< - React.ComponentProps - >), - }), - ); + render(getComponent({ onSelect })); await waitFor(() => screen.getByTestId('channel-preview-button')); @@ -115,14 +90,7 @@ describe('ChannelPreviewView', () => { const message = generateMessage(); await initializeChannel(generateChannelResponse()); - render( - getComponent({ - ...({ - latestMessage: message, - latestMessageLength: 6, - } as unknown as Partial>), - }), - ); + render(getComponent()); const expectedMessagePreview = truncate(message.text, { length: 6 }); await waitFor(() => screen.queryByText(expectedMessagePreview)); diff --git a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx index dcdcb2b959..75d9903c5a 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx @@ -88,15 +88,13 @@ describe('ImageGallery', () => { it('render image gallery component', async () => { render( , ); @@ -111,11 +109,9 @@ describe('ImageGallery', () => { render( , ); diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx index 0ba68f73de..db8aa4d513 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx @@ -4,7 +4,7 @@ import type { SharedValue } from 'react-native-reanimated'; import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; -import { Attachment, LocalMessage } from 'stream-chat'; +import { Attachment } from 'stream-chat'; import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import { @@ -53,7 +53,7 @@ const ImageGalleryComponentVideo = (props: ImageGalleryProps) => { messages: [ generateMessage({ attachments: [attachment], - }) as unknown as LocalMessage, + }), ], selectedAttachmentUrl: attachment.asset_url, }); @@ -95,7 +95,7 @@ const ImageGalleryComponentImage = ( messages: [ generateMessage({ attachments: [props.attachment], - }) as unknown as LocalMessage, + }), ], selectedAttachmentUrl: props.attachment.image_url as string, }); diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx index 96e32e69ea..88e6a194cf 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryGrid.test.tsx @@ -55,7 +55,7 @@ describe('ImageGalleryGrid', () => { it('should render ImageGalleryGrid', async () => { const message = generateMessage({ attachments: [generateImageAttachment(), generateImageAttachment()], - }) as unknown as LocalMessage; + }); render(); @@ -67,7 +67,7 @@ describe('ImageGalleryGrid', () => { it('should render ImageGalleryGrid individual images', async () => { const message = generateMessage({ attachments: [generateImageAttachment(), generateVideoAttachment({ type: 'video' })], - }) as unknown as LocalMessage; + }); render(); @@ -82,7 +82,7 @@ describe('ImageGalleryGrid', () => { const message = generateMessage({ attachments: [generateImageAttachment(), generateVideoAttachment({ type: 'video' })], - }) as unknown as LocalMessage; + }); render(); diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx index c5755f6757..d3ae35bd5f 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx @@ -4,8 +4,6 @@ import type { SharedValue } from 'react-native-reanimated'; import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; -import { LocalMessage } from 'stream-chat'; - import { ImageGalleryHeader as ImageGalleryHeaderDefault } from '../../../components/ImageGallery/components/ImageGalleryHeader'; import { ImageGalleryContext, @@ -36,7 +34,7 @@ const ImageGalleryComponent = (props: ImageGalleryProps) => { const [imageGalleryStateStore] = useState(() => new ImageGalleryStateStore()); const attachment = generateImageAttachment(); imageGalleryStateStore.openImageGallery({ - messages: [generateMessage({ attachments: [attachment] }) as unknown as LocalMessage], + messages: [generateMessage({ attachments: [attachment] })], selectedAttachmentUrl: attachment.image_url, }); diff --git a/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx b/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx index 42932b8ef3..5d38bcfc8e 100644 --- a/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx +++ b/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx @@ -3,8 +3,6 @@ import { SharedValue, useSharedValue } from 'react-native-reanimated'; import { render, renderHook, waitFor } from '@testing-library/react-native'; -import { LocalMessage } from 'stream-chat'; - import { ImageGalleryContext, ImageGalleryContextValue, @@ -24,7 +22,7 @@ const ImageGalleryComponentWrapper = ({ children }: PropsWithChildren) => { attachments: [attachment], // eslint-disable-next-line @typescript-eslint/no-explicit-any user: {} as any, - }) as unknown as LocalMessage, + }), ], selectedAttachmentUrl: (attachment as unknown as { url?: string }).url, }); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx index d0023a046e..49fbcbdb8e 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageAuthor.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; -import type { LocalMessage, StreamChat } from 'stream-chat'; +import type { StreamChat } from 'stream-chat'; import type { DeepPartial } from '../../../../contexts/themeContext/ThemeContext'; import type { Theme } from '../../../../contexts/themeContext/utils/theme'; @@ -31,13 +31,7 @@ describe('MessageAuthor', () => { }); render( }> - )} - message={message as unknown as LocalMessage} - /> + , ); @@ -47,13 +41,7 @@ describe('MessageAuthor', () => { screen.rerender( }> - )} - message={message as unknown as LocalMessage} - /> + , ); @@ -68,14 +56,7 @@ describe('MessageAuthor', () => { screen.rerender( }> - )} - message={staticMessage as unknown as LocalMessage} - showAvatar - /> + , ); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx index 2b24dc7a4b..19802c578f 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; -import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; @@ -24,15 +24,13 @@ import { Channel } from '../../../Channel/Channel'; import { Chat } from '../../../Chat/Chat'; import { Message } from '../../Message'; -const toLocalMessage = (m: unknown): LocalMessage => m as LocalMessage; - describe('MessageContent', () => { let channel: ChannelType; let chatClient: StreamChat; - let renderMessage: (options: { - message: unknown; - [key: string]: unknown; - }) => ReturnType; + let renderMessage: ( + options: Omit, 'groupStyles'> & + Partial, 'groupStyles'>>, + ) => ReturnType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ user })]; @@ -52,12 +50,7 @@ describe('MessageContent', () => { render( - )} - /> + , ); @@ -131,7 +124,7 @@ describe('MessageContent', () => { - + @@ -155,7 +148,7 @@ describe('MessageContent', () => { - + @@ -182,7 +175,7 @@ describe('MessageContent', () => { }} > - + @@ -210,7 +203,7 @@ describe('MessageContent', () => { }} > - + @@ -239,7 +232,7 @@ describe('MessageContent', () => { }} > - + @@ -264,7 +257,7 @@ describe('MessageContent', () => { }} > - + @@ -284,10 +277,7 @@ describe('MessageContent', () => { const user = generateUser(); const message = generateMessage({ user }); - renderMessage({ - message, - MessageFooter: null, - }); + renderMessage({ message }); await waitFor(() => { expect(screen.getByTestId('message-content-wrapper')).toBeTruthy(); @@ -463,13 +453,7 @@ describe('MessageContent', () => { - )} - /> + , diff --git a/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx index 00d513c323..9e3c7d487e 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessagePinnedHeader.test.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; -import type { LocalMessage } from 'stream-chat'; import type { DeepPartial } from '../../../../contexts/themeContext/ThemeContext'; import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext'; @@ -25,7 +24,7 @@ describe('MessagePinnedHeader', () => { }); render( }> - + , ); @@ -35,7 +34,7 @@ describe('MessagePinnedHeader', () => { screen.rerender( }> - + , ); @@ -46,7 +45,7 @@ describe('MessagePinnedHeader', () => { screen.rerender( }> - + , ); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx index b41fc08d2e..4f2c7963e3 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageReplies.test.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { cleanup, render, screen, userEvent, waitFor } from '@testing-library/react-native'; -import type { LocalMessage } from 'stream-chat'; import type { DeepPartial } from '../../../../contexts/themeContext/ThemeContext'; import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext'; @@ -12,7 +11,6 @@ import { TranslationProvider } from '../../../../contexts/translationContext/Tra import { generateMessage } from '../../../../mock-builders/generator/message'; import { generateStaticUser, generateUser } from '../../../../mock-builders/generator/user'; import { MessageReplies } from '../MessageReplies'; -import { MessageRepliesAvatars } from '../MessageRepliesAvatars'; afterEach(cleanup); @@ -29,15 +27,7 @@ describe('MessageReplies', () => { render( }> - )} - message={message as unknown as LocalMessage} - /> + , ); @@ -58,15 +48,7 @@ describe('MessageReplies', () => { screen.rerender( }> - )} - message={message2 as unknown as LocalMessage} - /> + , ); @@ -90,14 +72,7 @@ describe('MessageReplies', () => { render( }> - null, - } as unknown as React.ComponentProps)} - message={message as unknown as LocalMessage} - /> + null} /> , ); @@ -114,15 +89,7 @@ describe('MessageReplies', () => { screen.rerender( }> - null, - } as unknown as React.ComponentProps)} - message={message2 as unknown as LocalMessage} - threadList - /> + null} threadList /> , ); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx index 5e7a795028..e8ea53fab7 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { cleanup, render, waitFor } from '@testing-library/react-native'; -import type { Channel as ChannelType, LocalMessage, StreamChat } from 'stream-chat'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { Channel } from '../../..'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; @@ -75,7 +75,7 @@ describe('MessageStatus', () => { const { getByText, rerender, toJSON } = renderMessageStatus({ deliveredToCount: 2, - message: message as unknown as LocalMessage, + message, readBy, }); @@ -90,7 +90,7 @@ describe('MessageStatus', () => { - + , @@ -114,7 +114,7 @@ describe('MessageStatus', () => { const message = generateMessage({ user }); const { getByLabelText } = renderMessageStatus({ deliveredToCount, - message: { ...message, status } as unknown as LocalMessage, + message: { ...message, status }, readBy, }); await waitFor(() => { diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx index c834cfa7b0..dc0684ef8b 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageTextContainer.test.tsx @@ -3,8 +3,6 @@ import { Text } from 'react-native'; import { cleanup, render, waitFor } from '@testing-library/react-native'; -import { LocalMessage } from 'stream-chat'; - import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; import { OverlayProvider } from '../../../../contexts/overlayContext/OverlayProvider'; import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext'; @@ -33,7 +31,7 @@ describe('MessageTextContainer', () => { }); const { getByTestId, getByText, rerender, toJSON } = render( - + , ); @@ -49,7 +47,7 @@ describe('MessageTextContainer', () => { MessageText: ({ message }) => {message?.text}, }} > - + , ); @@ -66,7 +64,7 @@ describe('MessageTextContainer', () => { rerender( - + , ); diff --git a/package/src/components/MessageList/__tests__/MessageSystem.test.tsx b/package/src/components/MessageList/__tests__/MessageSystem.test.tsx index ab1bcebdf6..da2a6a20a2 100644 --- a/package/src/components/MessageList/__tests__/MessageSystem.test.tsx +++ b/package/src/components/MessageList/__tests__/MessageSystem.test.tsx @@ -2,8 +2,6 @@ import React from 'react'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; -import type { LocalMessage } from 'stream-chat'; - import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { defaultTheme } from '../../../contexts/themeContext/utils/theme'; import { TranslationProvider } from '../../../contexts/translationContext/TranslationContext'; @@ -17,8 +15,6 @@ afterEach(cleanup); let i18nInstance: Streami18n; -const toLocalMessage = (m: unknown): LocalMessage => m as LocalMessage; - describe('MessageSystem', () => { beforeAll(() => { i18nInstance = new Streami18n(); @@ -35,7 +31,7 @@ describe('MessageSystem', () => { [0]['value']} > - + , ); @@ -56,7 +52,7 @@ describe('MessageSystem', () => { [0]['value']} > - + , ); diff --git a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx index 3d38f99dad..6fb05aadde 100644 --- a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx +++ b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx @@ -70,12 +70,7 @@ describe('ScrollToBottomButton', () => { const { getByTestId, getByText } = render( - )} - onPress={() => null} - showNotification={true} - unreadCount={3} - /> + null} showNotification={true} unreadCount={3} /> , ); diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx index a7272b0344..2f19f68612 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx @@ -4,7 +4,7 @@ import { Text } from 'react-native'; import { fireEvent, render } from '@testing-library/react-native'; -import { LocalMessage, ReactionResponse } from 'stream-chat'; +import { ReactionResponse } from 'stream-chat'; import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import { @@ -35,7 +35,7 @@ const defaultProps = { message: { ...generateMessage(), reaction_groups: { like: { count: 1, sum_scores: 1 }, love: { count: 1, sum_scores: 1 } }, - } as unknown as LocalMessage, + }, supportedReactions: mockSupportedReactions, }; diff --git a/package/src/components/Thread/__tests__/Thread.test.tsx b/package/src/components/Thread/__tests__/Thread.test.tsx index 5bd513b8cf..ad35bd44ab 100644 --- a/package/src/components/Thread/__tests__/Thread.test.tsx +++ b/package/src/components/Thread/__tests__/Thread.test.tsx @@ -66,7 +66,7 @@ describe('Thread', () => { const thread = generateMessage({ cid, text: 'Thread Message Text' }); const parent_id = thread.id; const props = { - thread: thread as unknown as LocalMessage, + thread, }; const threadResponses = [ @@ -79,7 +79,7 @@ describe('Thread', () => { threadResponses as unknown as Parameters[0], ); - renderComponent({ channel, chatClient, props, thread: thread as unknown as LocalMessage }); + renderComponent({ channel, chatClient, props, thread }); const { getAllByText, getByText, queryByText } = screen; @@ -158,12 +158,7 @@ describe('Thread', () => { ['value']} > - + {(c) => { setLastRead = c.setLastRead; diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap index 049ff2af71..05fc28cb01 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap @@ -49,14 +49,11 @@ exports[`Thread should match thread snapshot 1`] = ` "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "error": null, "html": "

regular

", "id": "38ef6f7c-3090-5759-a37f-ab0053aadb96", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, - "quoted_message": null, - "reaction_groups": null, "status": "received", "text": "Message6", "type": "regular", @@ -78,14 +75,11 @@ exports[`Thread should match thread snapshot 1`] = ` "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "error": null, "html": "

regular

", "id": "516efa25-5d29-5c9a-ad2d-4cc183e785bd", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, - "quoted_message": null, - "reaction_groups": null, "status": "received", "text": "Message5", "type": "regular", @@ -108,14 +102,11 @@ exports[`Thread should match thread snapshot 1`] = ` "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "error": null, "html": "

regular

", "id": "516efa25-5d29-5c9a-ad2d-4cc183e785bd", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, - "quoted_message": null, - "reaction_groups": null, "status": "received", "text": "Message5", "type": "regular", @@ -136,14 +127,11 @@ exports[`Thread should match thread snapshot 1`] = ` "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "error": null, "html": "

regular

", "id": "38ef6f7c-3090-5759-a37f-ab0053aadb96", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, - "quoted_message": null, - "reaction_groups": null, "status": "received", "text": "Message6", "type": "regular", @@ -164,14 +152,11 @@ exports[`Thread should match thread snapshot 1`] = ` "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "error": null, "html": "

regular

", "id": "82a83b16-b611-527c-b3ac-765ef6220490", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, - "quoted_message": null, - "reaction_groups": null, "status": "received", "text": "Message4", "type": "regular", @@ -194,14 +179,11 @@ exports[`Thread should match thread snapshot 1`] = ` "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "error": null, "html": "

regular

", "id": "82a83b16-b611-527c-b3ac-765ef6220490", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, - "quoted_message": null, - "reaction_groups": null, "status": "received", "text": "Message4", "type": "regular", @@ -222,14 +204,11 @@ exports[`Thread should match thread snapshot 1`] = ` "cid": "messaging:test-channel", "created_at": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "error": null, "html": "

regular

", "id": "516efa25-5d29-5c9a-ad2d-4cc183e785bd", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, - "quoted_message": null, - "reaction_groups": null, "status": "received", "text": "Message5", "type": "regular", diff --git a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx index f1fa49ed31..fd5d332c28 100644 --- a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx @@ -2,8 +2,6 @@ import React from 'react'; import { act, cleanup, renderHook, waitFor } from '@testing-library/react-native'; -import { LocalMessage } from 'stream-chat'; - import { Chat } from '../../../components'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; @@ -244,7 +242,7 @@ describe("MessageInputContext's editMessage", () => { attachments: [generateLocalFileUploadAttachmentData()], cid: 'messaging:channel-id', text: 'test', - }) as unknown as LocalMessage; + }); const { result } = renderHook(() => useMessageInputContext(), { initialProps, diff --git a/package/src/mock-builders/api/deleteMessage.ts b/package/src/mock-builders/api/deleteMessage.ts index 2f3324e58d..37cc556c3e 100644 --- a/package/src/mock-builders/api/deleteMessage.ts +++ b/package/src/mock-builders/api/deleteMessage.ts @@ -1,4 +1,4 @@ -import type { MessageResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse } from 'stream-chat'; import { mockedApiResponse, type MockedApiResponse } from './utils'; @@ -10,7 +10,7 @@ import { generateMessage } from '../generator/message'; * api - /channels/{type}/{id}/message */ export const deleteMessageApi = ( - message: MessageResponse = generateMessage(), + message: MessageResponse | LocalMessage = generateMessage(), ): MockedApiResponse => { const result = { duration: 0.01, diff --git a/package/src/mock-builders/api/deleteReaction.ts b/package/src/mock-builders/api/deleteReaction.ts index 33d6312a67..8d893311b8 100644 --- a/package/src/mock-builders/api/deleteReaction.ts +++ b/package/src/mock-builders/api/deleteReaction.ts @@ -1,4 +1,4 @@ -import type { MessageResponse, ReactionResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse, ReactionResponse } from 'stream-chat'; import { mockedApiResponse, type MockedApiResponse } from './utils'; @@ -10,7 +10,7 @@ import { generateReaction } from '../generator/reaction'; * api - /messages/{id}/reaction */ export const deleteReactionApi = ( - message: MessageResponse, + message: MessageResponse | LocalMessage, reaction: ReactionResponse = generateReaction(), ): MockedApiResponse => { const result = { diff --git a/package/src/mock-builders/api/getOrCreateChannel.ts b/package/src/mock-builders/api/getOrCreateChannel.ts index 4648c16f68..12f5c708b9 100644 --- a/package/src/mock-builders/api/getOrCreateChannel.ts +++ b/package/src/mock-builders/api/getOrCreateChannel.ts @@ -2,18 +2,24 @@ import type { ChannelMemberResponse, ChannelResponse, DraftResponse, + LocalMessage, MessageResponse, ReadResponse, } from 'stream-chat'; import { mockedApiResponse, type MockedApiResponse } from './utils'; +// Mock message input is either a `MessageResponse` (server shape) or a +// `LocalMessage` (client shape — what `generateMessage` produces). The +// downstream stream-chat client formats these interchangeably. +type MockMessage = Partial | LocalMessage; + export type GetOrCreateChannelApiParams = { draft?: Partial; channel?: Partial; members?: Partial[]; - messages?: Partial[]; - pinnedMessages?: Partial[]; + messages?: MockMessage[]; + pinnedMessages?: MockMessage[]; read?: Partial[]; }; diff --git a/package/src/mock-builders/api/sendMessage.ts b/package/src/mock-builders/api/sendMessage.ts index 0299faa64c..d3d861dbdb 100644 --- a/package/src/mock-builders/api/sendMessage.ts +++ b/package/src/mock-builders/api/sendMessage.ts @@ -1,4 +1,4 @@ -import type { MessageResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse } from 'stream-chat'; import { mockedApiResponse, type MockedApiResponse } from './utils'; @@ -8,8 +8,14 @@ import { generateMessage } from '../generator/message'; * Returns the api response for sendMessage api. * * api - /channels/{type}/{id}/message + * + * Accepts either `MessageResponse` or `LocalMessage`; the mock infra treats + * them interchangeably at runtime, even though the real API shape is + * `MessageResponse`. */ -export const sendMessageApi = (message: MessageResponse = generateMessage()): MockedApiResponse => { +export const sendMessageApi = ( + message: MessageResponse | LocalMessage = generateMessage(), +): MockedApiResponse => { const result = { duration: 0.01, message, diff --git a/package/src/mock-builders/api/sendReaction.ts b/package/src/mock-builders/api/sendReaction.ts index 7690688f6a..2cf2fe3b3a 100644 --- a/package/src/mock-builders/api/sendReaction.ts +++ b/package/src/mock-builders/api/sendReaction.ts @@ -1,4 +1,4 @@ -import type { MessageResponse, ReactionResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse, ReactionResponse } from 'stream-chat'; import { mockedApiResponse, type MockedApiResponse } from './utils'; @@ -10,7 +10,7 @@ import { generateReaction } from '../generator/reaction'; * api - /messages/{id}/reaction */ export const sendReactionApi = ( - message: MessageResponse, + message: MessageResponse | LocalMessage, reaction: ReactionResponse = generateReaction(), ): MockedApiResponse => { const result = { diff --git a/package/src/mock-builders/api/threadReplies.ts b/package/src/mock-builders/api/threadReplies.ts index 8ec8e0c5d9..66d2e38aa3 100644 --- a/package/src/mock-builders/api/threadReplies.ts +++ b/package/src/mock-builders/api/threadReplies.ts @@ -1,4 +1,4 @@ -import type { MessageResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse } from 'stream-chat'; import { mockedApiResponse, type MockedApiResponse } from './utils'; @@ -7,7 +7,9 @@ import { mockedApiResponse, type MockedApiResponse } from './utils'; * * api - /messages/${parent_id}/replies */ -export const threadRepliesApi = (replies: MessageResponse[] = []): MockedApiResponse => { +export const threadRepliesApi = ( + replies: Array = [], +): MockedApiResponse => { const result = { messages: replies, }; diff --git a/package/src/mock-builders/event/messageDeleted.ts b/package/src/mock-builders/event/messageDeleted.ts index 519905371d..9c99fc7491 100644 --- a/package/src/mock-builders/event/messageDeleted.ts +++ b/package/src/mock-builders/event/messageDeleted.ts @@ -1,16 +1,22 @@ import { fromPartial } from '@total-typescript/shoehorn'; -import type { ChannelResponse, Event, MessageResponse, StreamChat } from 'stream-chat'; +import type { + ChannelResponse, + Event, + LocalMessage, + MessageResponse, + StreamChat, +} from 'stream-chat'; export default ( client: StreamChat, - message: MessageResponse, + message: MessageResponse | LocalMessage, channel: Partial = {}, ) => { client.dispatchEvent( fromPartial({ channel, cid: channel.cid, - message, + message: message as MessageResponse, type: 'message.deleted', }), ); diff --git a/package/src/mock-builders/event/messageNew.ts b/package/src/mock-builders/event/messageNew.ts index 54178b656a..b23a169272 100644 --- a/package/src/mock-builders/event/messageNew.ts +++ b/package/src/mock-builders/event/messageNew.ts @@ -1,9 +1,15 @@ import { fromPartial } from '@total-typescript/shoehorn'; -import type { ChannelResponse, Event, MessageResponse, StreamChat } from 'stream-chat'; +import type { + ChannelResponse, + Event, + LocalMessage, + MessageResponse, + StreamChat, +} from 'stream-chat'; export default ( client: StreamChat, - newMessage: MessageResponse, + newMessage: MessageResponse | LocalMessage, channel: Partial = {}, ) => { client.dispatchEvent( @@ -12,7 +18,7 @@ export default ( channel_id: channel.id, channel_type: channel.type, cid: channel.cid, - message: newMessage, + message: newMessage as MessageResponse, type: 'message.new', ...(newMessage.user ? { user: newMessage.user } : {}), }), diff --git a/package/src/mock-builders/event/messageUpdated.ts b/package/src/mock-builders/event/messageUpdated.ts index 127ed677eb..3ac3671d73 100644 --- a/package/src/mock-builders/event/messageUpdated.ts +++ b/package/src/mock-builders/event/messageUpdated.ts @@ -1,16 +1,22 @@ import { fromPartial } from '@total-typescript/shoehorn'; -import type { ChannelResponse, Event, MessageResponse, StreamChat } from 'stream-chat'; +import type { + ChannelResponse, + Event, + LocalMessage, + MessageResponse, + StreamChat, +} from 'stream-chat'; export default ( client: StreamChat, - newMessage: MessageResponse, + newMessage: MessageResponse | LocalMessage, channel: Partial = {}, ) => { client.dispatchEvent( fromPartial({ channel, cid: channel.cid, - message: newMessage, + message: newMessage as MessageResponse, type: 'message.updated', }), ); diff --git a/package/src/mock-builders/event/reactionDeleted.ts b/package/src/mock-builders/event/reactionDeleted.ts index 5598ddb72a..36c3c5eb27 100644 --- a/package/src/mock-builders/event/reactionDeleted.ts +++ b/package/src/mock-builders/event/reactionDeleted.ts @@ -2,6 +2,7 @@ import { fromPartial } from '@total-typescript/shoehorn'; import type { ChannelResponse, Event, + LocalMessage, MessageResponse, ReactionResponse, StreamChat, @@ -10,14 +11,14 @@ import type { export default ( client: StreamChat, reaction: ReactionResponse, - message: MessageResponse, + message: MessageResponse | LocalMessage, channel: Partial = {}, ) => { client.dispatchEvent( fromPartial({ channel, cid: channel.cid, - message, + message: message as MessageResponse, reaction, type: 'reaction.deleted', }), diff --git a/package/src/mock-builders/event/reactionNew.ts b/package/src/mock-builders/event/reactionNew.ts index 75f4708afc..d8d8b1cd29 100644 --- a/package/src/mock-builders/event/reactionNew.ts +++ b/package/src/mock-builders/event/reactionNew.ts @@ -2,6 +2,7 @@ import { fromPartial } from '@total-typescript/shoehorn'; import type { ChannelResponse, Event, + LocalMessage, MessageResponse, ReactionResponse, StreamChat, @@ -10,14 +11,14 @@ import type { export default ( client: StreamChat, reaction: ReactionResponse, - message: MessageResponse, + message: MessageResponse | LocalMessage, channel: Partial = {}, ) => { client.dispatchEvent( fromPartial({ channel, cid: channel.cid, - message, + message: message as MessageResponse, reaction, type: 'reaction.new', }), diff --git a/package/src/mock-builders/event/reactionUpdated.ts b/package/src/mock-builders/event/reactionUpdated.ts index 8e6ef9d339..f87344d1be 100644 --- a/package/src/mock-builders/event/reactionUpdated.ts +++ b/package/src/mock-builders/event/reactionUpdated.ts @@ -2,6 +2,7 @@ import { fromPartial } from '@total-typescript/shoehorn'; import type { ChannelResponse, Event, + LocalMessage, MessageResponse, ReactionResponse, StreamChat, @@ -10,14 +11,14 @@ import type { export default ( client: StreamChat, reaction: ReactionResponse, - message: MessageResponse, + message: MessageResponse | LocalMessage, channel: Partial = {}, ) => { client.dispatchEvent( fromPartial({ channel, cid: channel.cid, - message, + message: message as MessageResponse, reaction, type: 'reaction.updated', }), diff --git a/package/src/mock-builders/generator/channel.ts b/package/src/mock-builders/generator/channel.ts index 964ee857ec..3a91114b45 100644 --- a/package/src/mock-builders/generator/channel.ts +++ b/package/src/mock-builders/generator/channel.ts @@ -1,6 +1,7 @@ import type { ChannelMemberResponse, ChannelResponse, + LocalMessage, MessageResponse, ReadResponse, } from 'stream-chat'; @@ -114,10 +115,12 @@ export const generateChannel = ( return { ...accumulated, [current]: customValues[current] } as GeneratedChannel; }, getChannelDefaults()); +type ChannelResponseMessage = Partial | LocalMessage; + export type GeneratedChannelResponseCustomValues = { channel?: Partial; id?: string; - messages?: Partial[]; + messages?: ChannelResponseMessage[]; members?: Partial[]; read?: Partial[]; type?: string; diff --git a/package/src/mock-builders/generator/message.ts b/package/src/mock-builders/generator/message.ts index 16010b9826..13c24cb375 100644 --- a/package/src/mock-builders/generator/message.ts +++ b/package/src/mock-builders/generator/message.ts @@ -1,28 +1,32 @@ import { fromPartial } from '@total-typescript/shoehorn'; -import type { MessageResponse } from 'stream-chat'; +import type { LocalMessage } from 'stream-chat'; import { v4 as uuidv4, v5 as uuidv5 } from 'uuid'; import { generateUser } from './user'; -type GenerateMessageOptions = Partial & { timestamp?: Date }; +type GenerateMessageOptions = Partial & { timestamp?: Date }; -export const generateMessage = (options: GenerateMessageOptions = {}): MessageResponse => { +// Returns a `LocalMessage`-shaped mock. Components across this SDK consume +// `LocalMessage` (with `Date` objects for `created_at`/`updated_at`/`pinned_at`/ +// `deleted_at`), so the mock matches that shape. For tests that feed mock data +// into an API response where the server returns `MessageResponse` (strings for +// dates), cast at the call site — runtime values are the same either way. +export const generateMessage = (options: GenerateMessageOptions = {}): LocalMessage => { const timestamp = options.timestamp || new Date(new Date().getTime() - Math.floor(Math.random() * 100000)); - // NOTE: `created_at` / `updated_at` on `MessageResponse` are typed as `string`, - // but tests here treat the generated message as if it were a `LocalMessage` - // (where those fields are `Date`). Keeping `Date` objects at runtime preserves - // behavior of component code that calls e.g. `.toDateString()` on them. - return fromPartial({ + return fromPartial({ attachments: [], - created_at: timestamp as unknown as string, + created_at: timestamp, + deleted_at: null, html: '

regular

', id: uuidv4(), - message_text_updated_at: timestamp as unknown as string, + message_text_updated_at: timestamp.toISOString(), + pinned_at: null, + status: 'received', text: uuidv4(), type: 'regular', - updated_at: timestamp.toString(), + updated_at: timestamp, user: generateUser(), ...options, }); @@ -32,13 +36,15 @@ const StreamReactNativeNamespace = '9b244ee4-7d69-4d7b-ae23-cf89e9f7b035'; export const generateStaticMessage = ( seed: string, options?: GenerateMessageOptions, - date?: string, -): MessageResponse => - generateMessage({ - created_at: date || '2020-04-27T13:39:49.331742Z', + date?: string | Date, +): LocalMessage => { + const staticDate = date ? new Date(date) : new Date('2020-04-27T13:39:49.331742Z'); + return generateMessage({ + created_at: staticDate, id: uuidv5(seed, StreamReactNativeNamespace), - message_text_updated_at: date || '2020-04-27T13:39:49.331742Z', + message_text_updated_at: staticDate.toISOString(), text: seed, - updated_at: date || '2020-04-27T13:39:49.331742Z', + updated_at: staticDate, ...options, }); +}; diff --git a/package/src/state-store/__tests__/image-gallery-state-store.test.ts b/package/src/state-store/__tests__/image-gallery-state-store.test.ts index 901bb23577..76a620cd4b 100644 --- a/package/src/state-store/__tests__/image-gallery-state-store.test.ts +++ b/package/src/state-store/__tests__/image-gallery-state-store.test.ts @@ -1,4 +1,4 @@ -import type { Attachment, LocalMessage, UserResponse } from 'stream-chat'; +import type { Attachment, UserResponse } from 'stream-chat'; import { generateImageAttachment, @@ -41,9 +41,6 @@ const createGiphyAttachment = (overrides: Partial = {}): Attachment ...overrides, }); -const toLocalMessage = (msg: unknown): LocalMessage => msg as LocalMessage; -const toLocalMessages = (msgs: unknown[]): LocalMessage[] => msgs as LocalMessage[]; - describe('ImageGalleryStateStore', () => { beforeEach(() => { jest.clearAllMocks(); @@ -108,7 +105,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const messages = [generateMessage({ id: '1' }), generateMessage({ id: '2' })]; - store.messages = toLocalMessages(messages); + store.messages = messages; expect(store.messages).toEqual(messages); }); @@ -117,7 +114,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const messages = [generateMessage({ id: '1' })]; - store.messages = toLocalMessages(messages); + store.messages = messages; expect(store.state.getLatestValue().messages).toEqual(messages); }); @@ -197,7 +194,7 @@ describe('ImageGalleryStateStore', () => { }); const message = generateMessage({ attachments: [imageAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(1); expect(store.attachmentsWithMessage[0].attachments).toContain(imageAttachment); @@ -210,7 +207,7 @@ describe('ImageGalleryStateStore', () => { }); const message = generateMessage({ attachments: [videoAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(1); expect(store.attachmentsWithMessage[0].attachments).toContain(videoAttachment); @@ -221,7 +218,7 @@ describe('ImageGalleryStateStore', () => { const giphyAttachment = createGiphyAttachment(); const message = generateMessage({ attachments: [giphyAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(1); expect(store.attachmentsWithMessage[0].attachments).toContain(giphyAttachment); @@ -235,7 +232,7 @@ describe('ImageGalleryStateStore', () => { }); const message = generateMessage({ attachments: [videoAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -248,7 +245,7 @@ describe('ImageGalleryStateStore', () => { }); const message = generateMessage({ attachments: [linkPreviewAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -261,7 +258,7 @@ describe('ImageGalleryStateStore', () => { }); const message = generateMessage({ attachments: [linkAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -275,7 +272,7 @@ describe('ImageGalleryStateStore', () => { }); const message = generateMessage({ attachments: [viewableImage, linkPreview], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(1); }); @@ -288,7 +285,7 @@ describe('ImageGalleryStateStore', () => { }; const message = generateMessage({ attachments: [fileAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -297,7 +294,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const message = generateMessage({ attachments: [null as unknown as Attachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -306,7 +303,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const message = generateMessage({ attachments: undefined, id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(0); }); @@ -352,7 +349,7 @@ describe('ImageGalleryStateStore', () => { user_id: user.id, }); - store.messages = toLocalMessages([message]); + store.messages = [message]; const assets = store.assets; expect(assets).toHaveLength(1); @@ -377,7 +374,7 @@ describe('ImageGalleryStateStore', () => { }); const message = generateMessage({ attachments: [videoAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; const assets = store.assets; expect(assets).toHaveLength(1); @@ -393,7 +390,7 @@ describe('ImageGalleryStateStore', () => { const giphyAttachment = createGiphyAttachment(); const message = generateMessage({ attachments: [giphyAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; const assets = store.assets; expect(assets).toHaveLength(1); @@ -410,7 +407,7 @@ describe('ImageGalleryStateStore', () => { const attachment2 = generateImageAttachment({ image_url: 'https://example.com/image2.jpg' }); const message = generateMessage({ attachments: [attachment1, attachment2], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; const assets = store.assets; expect(assets).toHaveLength(2); @@ -428,7 +425,7 @@ describe('ImageGalleryStateStore', () => { }; const message = generateMessage({ attachments: [giphyAttachment], id: '1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(getUrlOfImageAttachment(giphyAttachment, 'original')).toBe( 'https://giphy.com/original.gif', @@ -449,7 +446,7 @@ describe('ImageGalleryStateStore', () => { id: '2', }); - store.messages = toLocalMessages([message1, message2]); + store.messages = [message1, message2]; expect(store.assets).toHaveLength(3); }); @@ -461,8 +458,8 @@ describe('ImageGalleryStateStore', () => { const initialMessages = [generateMessage({ id: 'msg-1' })]; const newMessages = [generateMessage({ id: 'msg-2' }), generateMessage({ id: 'msg-3' })]; - store.messages = toLocalMessages(initialMessages); - store.appendMessages(toLocalMessages(newMessages)); + store.messages = initialMessages; + store.appendMessages(newMessages); expect(store.messages).toHaveLength(3); expect(store.messages).toEqual([...initialMessages, ...newMessages]); @@ -472,7 +469,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const newMessages = [generateMessage({ id: 'msg-1' })]; - store.appendMessages(toLocalMessages(newMessages)); + store.appendMessages(newMessages); expect(store.messages).toEqual(newMessages); }); @@ -481,7 +478,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const initialMessages = [generateMessage({ id: 'msg-1' })]; - store.messages = toLocalMessages(initialMessages); + store.messages = initialMessages; store.appendMessages([]); expect(store.messages).toEqual(initialMessages); @@ -495,8 +492,8 @@ describe('ImageGalleryStateStore', () => { const message2 = generateMessage({ id: 'msg-2' }); const message3 = generateMessage({ id: 'msg-3' }); - store.messages = toLocalMessages([message1, message2, message3]); - store.removeMessages([toLocalMessage(message2)]); + store.messages = [message1, message2, message3]; + store.removeMessages([message2]); expect(store.messages).toHaveLength(2); expect(store.messages).toEqual([message1, message3]); @@ -508,8 +505,8 @@ describe('ImageGalleryStateStore', () => { const message2 = generateMessage({ id: 'msg-2' }); const message3 = generateMessage({ id: 'msg-3' }); - store.messages = toLocalMessages([message1, message2, message3]); - store.removeMessages(toLocalMessages([message1, message3])); + store.messages = [message1, message2, message3]; + store.removeMessages([message1, message3]); expect(store.messages).toHaveLength(1); expect(store.messages).toEqual([message2]); @@ -521,8 +518,8 @@ describe('ImageGalleryStateStore', () => { const message2 = generateMessage({ id: 'msg-2' }); const nonExistentMessage = generateMessage({ id: 'non-existent' }); - store.messages = toLocalMessages([message1, message2]); - store.removeMessages([toLocalMessage(nonExistentMessage)]); + store.messages = [message1, message2]; + store.removeMessages([nonExistentMessage]); expect(store.messages).toHaveLength(2); expect(store.messages).toEqual([message1, message2]); @@ -532,7 +529,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const message1 = generateMessage({ id: 'msg-1' }); - store.messages = toLocalMessages([message1]); + store.messages = [message1]; store.removeMessages([]); expect(store.messages).toEqual([message1]); @@ -551,7 +548,7 @@ describe('ImageGalleryStateStore', () => { const selectedUrl = 'https://example.com/1.jpg'; store.openImageGallery({ - messages: toLocalMessages(messages), + messages, selectedAttachmentUrl: selectedUrl, }); @@ -563,7 +560,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const messages = [generateMessage({ id: 'msg-1' })]; - store.openImageGallery({ messages: toLocalMessages(messages) }); + store.openImageGallery({ messages }); expect(store.messages).toEqual(messages); expect(store.selectedAttachmentUrl).toBeUndefined(); @@ -574,8 +571,8 @@ describe('ImageGalleryStateStore', () => { const oldMessages = [generateMessage({ id: 'msg-1' })]; const newMessages = [generateMessage({ id: 'msg-2' })]; - store.messages = toLocalMessages(oldMessages); - store.openImageGallery({ messages: toLocalMessages(newMessages) }); + store.messages = oldMessages; + store.openImageGallery({ messages: newMessages }); expect(store.messages).toEqual(newMessages); }); @@ -590,7 +587,7 @@ describe('ImageGalleryStateStore', () => { attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.state.getLatestValue().assets).toHaveLength(1); @@ -610,22 +607,20 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const unsubscribe = store.subscribeToMessages(); - store.messages = toLocalMessages([ + store.messages = [ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]); + ]; expect(store.state.getLatestValue().assets).toHaveLength(1); - store.appendMessages( - toLocalMessages([ - generateMessage({ - attachments: [generateImageAttachment({ image_url: 'https://example.com/2.jpg' })], - id: 'msg-2', - }), - ]), - ); + store.appendMessages([ + generateMessage({ + attachments: [generateImageAttachment({ image_url: 'https://example.com/2.jpg' })], + id: 'msg-2', + }), + ]); expect(store.state.getLatestValue().assets).toHaveLength(2); unsubscribe(); @@ -653,7 +648,7 @@ describe('ImageGalleryStateStore', () => { }), ]; - store.messages = toLocalMessages(messages); + store.messages = messages; store.selectedAttachmentUrl = 'https://example.com/2.jpg'; expect(store.state.getLatestValue().currentIndex).toBe(1); @@ -666,12 +661,12 @@ describe('ImageGalleryStateStore', () => { store.subscribeToMessages(); const unsubscribe = store.subscribeToSelectedAttachmentUrl(); - store.messages = toLocalMessages([ + store.messages = [ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]); + ]; store.selectedAttachmentUrl = 'https://example.com/non-existent.jpg'; expect(store.state.getLatestValue().currentIndex).toBe(0); @@ -696,14 +691,14 @@ describe('ImageGalleryStateStore', () => { store.subscribeToMessages(); const unsubscribe = store.subscribeToSelectedAttachmentUrl(); - store.messages = toLocalMessages([ + store.messages = [ generateMessage({ attachments: [ generateImageAttachment({ image_url: 'https://example.com/image.jpg?size=small' }), ], id: 'msg-1', }), - ]); + ]; store.selectedAttachmentUrl = 'https://example.com/image.jpg?size=large'; expect(store.state.getLatestValue().currentIndex).toBe(0); @@ -727,12 +722,12 @@ describe('ImageGalleryStateStore', () => { const unsubscribe = store.registerSubscriptions(); // Test that message subscription is working - store.messages = toLocalMessages([ + store.messages = [ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]); + ]; expect(store.state.getLatestValue().assets).toHaveLength(1); // Test that selectedAttachmentUrl subscription is working @@ -767,12 +762,12 @@ describe('ImageGalleryStateStore', () => { describe('clear', () => { it('should reset state to initial values', () => { const store = new ImageGalleryStateStore(); - store.messages = toLocalMessages([ + store.messages = [ generateMessage({ attachments: [generateImageAttachment({ image_url: 'https://example.com/1.jpg' })], id: 'msg-1', }), - ]); + ]; store.selectedAttachmentUrl = 'https://example.com/1.jpg'; store.currentIndex = 5; @@ -803,9 +798,9 @@ describe('ImageGalleryStateStore', () => { id: 'msg-1', }), user: undefined, - } as unknown as LocalMessage; + }; - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.assets).toHaveLength(1); expect(store.assets[0].user).toBeUndefined(); @@ -815,11 +810,8 @@ describe('ImageGalleryStateStore', () => { const store1 = new ImageGalleryStateStore({ autoPlayVideo: true }); const store2 = new ImageGalleryStateStore({ autoPlayVideo: false }); - store1.messages = toLocalMessages([generateMessage({ id: 'msg-1' })]); - store2.messages = toLocalMessages([ - generateMessage({ id: 'msg-2' }), - generateMessage({ id: 'msg-3' }), - ]); + store1.messages = [generateMessage({ id: 'msg-1' })]; + store2.messages = [generateMessage({ id: 'msg-2' }), generateMessage({ id: 'msg-3' })]; expect(store1.messages).toHaveLength(1); expect(store2.messages).toHaveLength(2); @@ -832,14 +824,14 @@ describe('ImageGalleryStateStore', () => { store.subscribeToMessages(); for (let i = 0; i < 100; i++) { - store.messages = toLocalMessages([ + store.messages = [ generateMessage({ attachments: [ generateImageAttachment({ image_url: `https://example.com/image-${i}.jpg` }), ], id: `msg-${i}`, }), - ]); + ]; } expect(store.state.getLatestValue().assets).toHaveLength(1); @@ -850,7 +842,7 @@ describe('ImageGalleryStateStore', () => { const store = new ImageGalleryStateStore(); const message = generateMessage({ attachments: [], id: 'msg-1' }); - store.messages = toLocalMessages([message]); + store.messages = [message]; expect(store.attachmentsWithMessage).toHaveLength(0); expect(store.assets).toEqual([]); @@ -873,7 +865,7 @@ describe('ImageGalleryStateStore', () => { }), ]; - store.messages = toLocalMessages(messages); + store.messages = messages; const assets = store.assets; expect(assets[0].uri).toBe('https://example.com/first.jpg'); @@ -893,7 +885,7 @@ describe('ImageGalleryStateStore', () => { ); const newMessages = [generateMessage({ id: 'msg-1' })]; - store.messages = toLocalMessages(newMessages); + store.messages = newMessages; expect(callback).toHaveBeenCalledWith(newMessages); }); From 1e546316b577dd6ac29181e6d3318013eb99e280 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 16:04:21 +0200 Subject: [PATCH 11/15] refactor(tests): strip more dead props / redundant casts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second pass of the dead-props sweep now that the low-hanging spread+cast pattern is gone. - MessageList.test.tsx: remove \`channelUnreadState\` prop from \`\`. Not on \`MessageList\`'s type — it's an internal state derived from \`channelUnreadStateStore\`. The test was silently passing it hoping to override something; nothing consumed it. Also drop the now-unused \`messageListProps\` cast. - Thread.test.tsx: drop \`client={chatClient}\` from \`\`. \`Channel\` doesn't declare \`client\` as a prop — it reads it from \`ChatContext\`. The prop was silently accepted via structural typing and ignored at runtime. - ImageGalleryHeader.test.tsx: drop two \`// @ts-ignore\` lines above JSX props. The underlying issue was that \`sharedValueOpacity\`/\`sharedValueVisible\` were \`SharedValue< number> | undefined\` (uninitialized let). Added \`!\` non-null assertion instead — the \`renderHook\` synchronously assigns them. - MessageUserReactionsAvatar.test.tsx: drop \`as unknown as ComponentProps['style']\` on the \`style\` prop. Use the direct \`defaultTheme as DeepPartial\` cast (same pattern used by other tests in the repo). test:typecheck: 0 errors. Full test suite: same pre-existing SQLite-isolation flake. --- .../__tests__/ImageGalleryHeader.test.tsx | 7 +------ .../MessageList/__tests__/MessageList.test.tsx | 10 +--------- .../__tests__/MessageUserReactionsAvatar.test.tsx | 14 +++++--------- .../components/Thread/__tests__/Thread.test.tsx | 2 +- 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx b/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx index 5d38bcfc8e..808cf5847a 100644 --- a/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx +++ b/package/src/components/ImageGallery/components/__tests__/ImageGalleryHeader.test.tsx @@ -50,12 +50,7 @@ it('doesnt fail if fromNow is not available on first render', async () => { }); const { getAllByText } = render( - + , ); await waitFor(() => { diff --git a/package/src/components/MessageList/__tests__/MessageList.test.tsx b/package/src/components/MessageList/__tests__/MessageList.test.tsx index 54cab06c78..de9dc1dd5a 100644 --- a/package/src/components/MessageList/__tests__/MessageList.test.tsx +++ b/package/src/components/MessageList/__tests__/MessageList.test.tsx @@ -311,25 +311,17 @@ describe('MessageList', () => { const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); - const channelUnreadState = { - last_read: new Date(), - unread_messages: 0, - }; - channel.state = { ...channelInitialState, latestMessages: [], messages, } as unknown as typeof channel.state; - const messageListProps = { channelUnreadState } as unknown as React.ComponentProps< - typeof MessageList - >; const { queryByLabelText } = render( - + , diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx index e5157e19a1..3214bc4342 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactionsAvatar.test.tsx @@ -1,9 +1,11 @@ -import React, { ComponentProps } from 'react'; +import React from 'react'; import { render } from '@testing-library/react-native'; import type { StreamChat } from 'stream-chat'; +import type { DeepPartial } from '../../../contexts/themeContext/ThemeContext'; +import type { Theme } from '../../../contexts/themeContext/utils/theme'; import { defaultTheme } from '../../../contexts/themeContext/utils/theme'; import { getTestClientWithUser } from '../../../mock-builders/mock'; import { Chat } from '../../Chat/Chat'; @@ -19,10 +21,7 @@ describe('MessageUserReactionsAvatar', () => { it('should render Avatar with correct image, name, and default size', () => { const { queryByTestId } = render( - ['style']} - > + }> , ); @@ -33,10 +32,7 @@ describe('MessageUserReactionsAvatar', () => { it('should render Avatar with correct image, name, and custom size', () => { const { queryByTestId } = render( - ['style']} - > + }> , ); diff --git a/package/src/components/Thread/__tests__/Thread.test.tsx b/package/src/components/Thread/__tests__/Thread.test.tsx index ad35bd44ab..21de58153c 100644 --- a/package/src/components/Thread/__tests__/Thread.test.tsx +++ b/package/src/components/Thread/__tests__/Thread.test.tsx @@ -158,7 +158,7 @@ describe('Thread', () => { ['value']} > - + {(c) => { setLastRead = c.setLastRead; From 58c782d1aa6c1d724061e0f5f03fb09f0c4ce17e Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 16:09:29 +0200 Subject: [PATCH 12/15] refactor(tests): remove last @ts-ignore directives from test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zero \`@ts-ignore\` / \`@ts-expect-error\` suppressions remain under src/**/*.test.*. - getResizedImageUrl.test.ts: typed the mocked URL object as \`as unknown as URL\` at the return value instead of silencing the search-params shape mismatch inline. - renderText.test.tsx: - Dropped \`@ts-ignore\` on the \`simple-markdown\` import (\`ASTNode\`/\`SingleASTNode\` are declared in the bundled \`simple-markdown.d.ts\` — the ignore was stale). - Replaced \`{node}\` with \`{JSON.stringify(node)}\` so the \`ASTNode\` value is a valid React child; the test only asserts \`getByText\`, the content shape is incidental. --- .../MessageItemView/utils/renderText.test.tsx | 6 ++---- .../utils/__tests__/getResizedImageUrl.test.ts | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package/src/components/Message/MessageItemView/utils/renderText.test.tsx b/package/src/components/Message/MessageItemView/utils/renderText.test.tsx index 0d842b0734..e6f5d24301 100644 --- a/package/src/components/Message/MessageItemView/utils/renderText.test.tsx +++ b/package/src/components/Message/MessageItemView/utils/renderText.test.tsx @@ -5,8 +5,7 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { render, waitFor, within } from '@testing-library/react-native'; -// @ts-ignore -import { ASTNode, SingleASTNode } from 'simple-markdown'; +import type { ASTNode, SingleASTNode } from 'simple-markdown'; import { ListOutput, ListOutputProps } from './renderText'; @@ -26,8 +25,7 @@ describe('list', () => { type: 'text', }); - // @ts-ignore - const mockOutput = (node: ASTNode) => {node}; + const mockOutput = (node: ASTNode) => {JSON.stringify(node)}; const MockText = ({ node, output, state }: ListOutputProps) => ( <> diff --git a/package/src/utils/__tests__/getResizedImageUrl.test.ts b/package/src/utils/__tests__/getResizedImageUrl.test.ts index 1ce08d0b23..585a2e185e 100644 --- a/package/src/utils/__tests__/getResizedImageUrl.test.ts +++ b/package/src/utils/__tests__/getResizedImageUrl.test.ts @@ -61,14 +61,16 @@ describe('getResizedImageUrl (sad flow)', () => { it('handles an error correctly and log warns it', () => { let someError; jest.spyOn(console, 'warn'); - jest.spyOn(global, 'URL').mockImplementationOnce(() => ({ - // @ts-ignore - searchParams: { - get: () => { - throw (someError = new Error('some error')); - }, - }, - })); + jest.spyOn(global, 'URL').mockImplementationOnce( + () => + ({ + searchParams: { + get: () => { + throw (someError = new Error('some error')); + }, + }, + }) as unknown as URL, + ); const resizedUrl = getResizedImageUrl({ url: TEST_URL_1, }); From e43ed0493cb4cd5362ea61741077661a31bacba0 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 20:14:15 +0200 Subject: [PATCH 13/15] refactor(tests): enable noImplicitAny and annotate implicit-any sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flips \`noImplicitAny: true\` in \`tsconfig.test.json\` (removes the override — now inherited from the base config) and fixes the ~630 resulting errors across ~20 test files. All annotations use SDK types from \`stream-chat\` where applicable; \`as unknown as T\` casts reserved for spots where the SDK type doesn't fit cleanly at the test boundary. Patterns applied: - \`let client\`, \`let channel\`, \`let chatClient\` → \`StreamChat\` / \`Channel\` / \`StreamChat\`. - \`renderComponent\` / \`Wrapper\` / \`renderMessage\` helper signatures typed via \`ComponentProps\`, \`Partial\`, \`ComponentOverrides\`, or \`PropsWithChildren>\`. - Context-consumer arrows (\`({ fn }) => ...\`) typed with \`ChatContextValue\` / \`TranslationContextValue\`. - Translation \`obj[key]\` → \`obj[key as keyof typeof obj]\`. - Uninitialized locals typed with a definite-assigned cast (\`{} as ChatContextValue\`) to match the established pattern. - \`_fiber\` and \`offlineDb.syncManager.invokeSyncStatusListeners\` accesses handled through narrow local shim types (\`TestOfflineDb\`, \`TestSyncManager\`) rather than widening the real SDK types. - \`updatedMessage\` \`Array.find\` results narrowed with \`!\` at assertion sites. Notable scope decisions (kept out of scope): - \`Channel\`'s \`doUpdateMessageRequest\` callback signature mismatches the SDK — cast the arg to \`Awaited>\` at the mock site to preserve runtime behavior. - \`messageComposer.compose\` mock resolves with \`{ localMessage, message }\` (no \`options\`) — dropped the dead \`options\` key; the SDK's \`MessageComposerMiddlewareState\` doesn't include it. - Removed unused \`TestWsConnection\` type in \`offline-feature.tsx\` and the unused \`MessageInputContextValue\` / \`MessagesContextValue\` type-imports in \`optimistic-update.tsx\` to satisfy \`@typescript-eslint/no-unused-vars\`. test:typecheck: 636 → 0. Full test suite: same pre-existing SQLite-isolation flake in \`offline-support/index.test.ts\`; no regressions. \`yarn lint\` / \`yarn build\` clean. Closes #3563. --- .../offline-support/offline-feature.tsx | 285 +++++++++++------- .../offline-support/optimistic-update.tsx | 123 +++++--- .../Attachment/__tests__/Giphy.test.tsx | 38 ++- .../__tests__/AutoCompleteInput.test.tsx | 20 +- .../components/Chat/__tests__/Chat.test.tsx | 36 ++- .../__tests__/Message.test.tsx | 15 +- .../__tests__/MessageContent.test.tsx | 8 +- .../__tests__/MessageItemView.test.tsx | 13 +- .../__tests__/ReactionListBottom.test.tsx | 13 +- .../__tests__/ReactionListTop.test.tsx | 10 +- .../__tests__/AttachButton.test.tsx | 18 +- .../__tests__/InputButtons.test.tsx | 18 +- .../__tests__/MessageComposer.test.tsx | 18 +- .../__tests__/SendButton.test.tsx | 17 +- .../__tests__/MessageList.test.tsx | 4 +- .../__tests__/MessageReactionPicker.test.tsx | 8 +- .../__tests__/filePickers.test.tsx | 25 +- .../__tests__/sendMessage.test.tsx | 32 +- .../__tests__/audio-player.test.ts | 4 +- .../src/utils/__tests__/Streami18n.test.ts | 12 +- package/src/utils/__tests__/utils.test.ts | 2 +- package/tsconfig.test.json | 6 +- 22 files changed, 484 insertions(+), 241 deletions(-) diff --git a/package/src/__tests__/offline-support/offline-feature.tsx b/package/src/__tests__/offline-support/offline-feature.tsx index 304ba5f0f9..973544c23d 100644 --- a/package/src/__tests__/offline-support/offline-feature.tsx +++ b/package/src/__tests__/offline-support/offline-feature.tsx @@ -6,14 +6,33 @@ import { Text, View } from 'react-native'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; import type { + Channel as ChannelLLC, + ChannelFilters, ChannelMemberResponse, ChannelSort, Event, + LocalMessage, MessageResponse, ReactionResponse, + StreamChat, + UserResponse, } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; +// Tests exercise internal APIs on StreamChat (private sync manager, legacy `wsConnection`). +// These helpers expose the internals at call sites without polluting the whole file with +// `any`; they use `as unknown as` because intersecting with the private `syncManager` +// collapses to `never`. +type TestSyncManager = { invokeSyncStatusListeners: (recovered: boolean) => Promise }; +const getSyncManager = (client: StreamChat): TestSyncManager => + (client.offlineDb as unknown as { syncManager: TestSyncManager }).syncManager; +const asHydrateChannelsMock = ( + client: StreamChat, +): StreamChat['hydrateActiveChannels'] & { mock: { calls: unknown[][] } } => + client.hydrateActiveChannels as StreamChat['hydrateActiveChannels'] & { + mock: { calls: unknown[][] }; + }; + import { ChannelList } from '../../components/ChannelList/ChannelList'; import { Chat } from '../../components/Chat/Chat'; import { WithComponents } from '../../contexts/componentsContext/ComponentsContext'; @@ -59,7 +78,7 @@ import { BetterSqlite } from '../../test-utils/BetterSqlite'; * Custom ChannelPreview component used via WithComponents. * Receives { channel, muted, unread, lastMessage } from ChannelPreview. */ -const ChannelPreviewComponent = ({ channel }) => ( +const ChannelPreviewComponent = ({ channel }: { channel: ChannelLLC }) => ( {channel.data?.name} {channel.state?.messages?.[0]?.text} @@ -70,7 +89,7 @@ test('Workaround to allow exporting tests', () => expect(true).toBe(true)); export const Generic = () => { describe('Offline support is disabled', () => { - let chatClient; + let chatClient: StreamChat; beforeAll(async () => { jest.clearAllMocks(); @@ -107,15 +126,31 @@ export const Generic = () => { }); describe('Offline support is enabled', () => { - let chatClient; - let channels; - - let allUsers; - let allMessages; - let allMembers; - let allReactions; - let allReads; - const getRandomInt = (lower, upper) => Math.floor(lower + Math.random() * (upper - lower + 1)); + // Generated channel response shape used throughout the tests. Widened to include the + // `cid` top-level field that is not part of `GeneratedChannelResponseCustomValues` but + // which the tests rely on. + type GeneratedChannelResponseWithCid = ReturnType & { + cid: string; + }; + + type MemberWithCid = ChannelMemberResponse & { cid: string }; + type ReadWithCid = { + cid: string; + last_read: Date; + unread_messages: number; + user: ChannelMemberResponse['user']; + }; + + let chatClient: StreamChat; + let channels: GeneratedChannelResponseWithCid[]; + + let allUsers: UserResponse[]; + let allMessages: Array | LocalMessage>; + let allMembers: MemberWithCid[]; + let allReactions: ReactionResponse[]; + let allReads: ReadWithCid[]; + const getRandomInt = (lower: number, upper: number) => + Math.floor(lower + Math.random() * (upper - lower + 1)); const createChannel = (messagesOverride?: Partial[]) => { const id = uuidv4(); const cid = `messaging:${id}`; @@ -124,19 +159,19 @@ export const Generic = () => { const begin = getRandomInt(0, allUsers.length - 3); // begin shouldn't be the end of users.length const end = getRandomInt(begin + 2, allUsers.length - 1); const usersForMembers = allUsers.slice(begin, end); - const members = usersForMembers.map( - (user) => + const members: MemberWithCid[] = usersForMembers.map( + (user: UserResponse) => // `cid` is not part of `ChannelMemberResponse`, but tests rely on reading it back from // the generated member objects — keep the runtime shape and widen the type. ({ ...generateMember({ user }), cid, - }) as unknown as ChannelMemberResponse & { cid: string }, + }) as unknown as MemberWithCid, ); members.push({ - ...generateMember({ user: chatClient.user }), + ...generateMember({ user: chatClient.user as UserResponse }), cid, - } as unknown as ChannelMemberResponse & { cid: string }); + } as unknown as MemberWithCid); const messages = messagesOverride || @@ -150,7 +185,7 @@ export const Generic = () => { const end = getRandomInt(begin + 1, usersForMembers.length - 1); const usersForReactions = usersForMembers.slice(begin, end); - const reactions = usersForReactions.map((user) => + const reactions = usersForReactions.map((user: UserResponse) => generateReaction({ message_id: id, user, @@ -166,7 +201,7 @@ export const Generic = () => { }); }); - const reads = members.map((member) => ({ + const reads: ReadWithCid[] = members.map((member: MemberWithCid) => ({ cid, last_read: new Date(new Date().setDate(new Date().getDate() - getRandomInt(0, 20))), unread_messages: 0, @@ -186,16 +221,16 @@ export const Generic = () => { members, messages, read: reads, - } as unknown as Parameters[0]) as ReturnType< + } as unknown as Parameters< typeof generateChannelResponse - > & { cid: string }; + >[0]) as GeneratedChannelResponseWithCid; }; beforeEach(async () => { jest.clearAllMocks(); chatClient = await getTestClientWithUser({ id: 'dan' }); allUsers = Array(20).fill(1).map(generateUser); - allUsers.push(chatClient.user); + allUsers.push(chatClient.user as UserResponse); allMessages = []; allMembers = []; allReactions = []; @@ -219,7 +254,7 @@ export const Generic = () => { const filters = { foo: 'bar', type: 'messaging', - }; + } as unknown as ChannelFilters; const sort: ChannelSort = { last_updated: 1 }; const renderComponent = () => @@ -231,7 +266,9 @@ export const Generic = () => { , ); - const expectCIDsOnUIToBeInDB = async (queryAllByLabelText) => { + const expectCIDsOnUIToBeInDB = async ( + queryAllByLabelText: typeof screen.queryAllByLabelText, + ) => { const channelIdsOnUI = queryAllByLabelText('list-item').map( (node) => (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber.pendingProps @@ -248,14 +285,16 @@ export const Generic = () => { expect(filterSortQueryInDB).toBe(actualFilterSortQueryInDB); expect(cidsInDB.length).toBe(channelIdsOnUI.length); - channelIdsOnUI.forEach((cidOnUi, index) => { + channelIdsOnUI.forEach((cidOnUi: string, index: number) => { expect(cidsInDB.includes(cidOnUi)).toBe(true); expect(index).toBe(cidsInDB.indexOf(cidOnUi)); }); }); }; - const expectAllChannelsWithStateToBeInDB = async (queryAllByLabelText) => { + const expectAllChannelsWithStateToBeInDB = async ( + queryAllByLabelText: typeof screen.queryAllByLabelText, + ) => { const channelIdsOnUI = queryAllByLabelText('list-item').map( (node) => (node as unknown as { _fiber: { pendingProps: { testID: string } } })._fiber.pendingProps @@ -277,26 +316,32 @@ export const Generic = () => { expect(reactionsRows.length).toBe(allReactions.length); channelsRows.forEach((row) => { - expect(channelIdsOnUI.includes(row.cid)).toBe(true); + expect(channelIdsOnUI.includes(row.cid as string)).toBe(true); }); messagesRows.forEach((row) => { - expect(allMessages.filter((m) => m.id === row.id)).toHaveLength(1); + expect( + allMessages.filter((m: Partial | LocalMessage) => m.id === row.id), + ).toHaveLength(1); }); membersRows.forEach((row) => expect( - allMembers.filter((m) => m.cid === row.cid && m.user.id === row.userId), + allMembers.filter((m: MemberWithCid) => m.cid === row.cid && m.user?.id === row.userId), ).toHaveLength(1), ); - usersRows.forEach((row) => expect(allUsers.filter((u) => u.id === row.id)).toHaveLength(1)); + usersRows.forEach((row) => + expect(allUsers.filter((u: UserResponse) => u.id === row.id)).toHaveLength(1), + ); reactionsRows.forEach((row) => expect( - allReactions.filter((r) => r.message_id === row.messageId && row.userId === r.user_id), + allReactions.filter( + (r: ReactionResponse) => r.message_id === row.messageId && row.userId === r.user_id, + ), ).toHaveLength(1), ); readsRows.forEach((row) => expect( - allReads.filter((r) => r.user.id === row.userId && r.cid === row.cid), + allReads.filter((r: ReadWithCid) => r.user?.id === row.userId && r.cid === row.cid), ).toHaveLength(1), ); }); @@ -325,7 +370,7 @@ export const Generic = () => { await act(() => dispatchConnectionChangedEvent(chatClient, false)); // await waiter(); await act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(async () => { expect(screen.getByTestId('channel-list-view')).toBeTruthy(); @@ -339,7 +384,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor( async () => { @@ -359,13 +404,11 @@ export const Generic = () => { await waitFor(async () => { act(() => dispatchConnectionChangedEvent(chatClient)); - await act( - async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true), - ); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); expect(screen.getByTestId('channel-list-view')).toBeTruthy(); expect(screen.getByTestId(emptyChannel.cid)).toBeTruthy(); expect(chatClient.hydrateActiveChannels).toHaveBeenCalled(); - expect(chatClient.hydrateActiveChannels.mock.calls[0][0]).toStrictEqual([emptyChannel]); + expect(asHydrateChannelsMock(chatClient).mock.calls[0][0]).toStrictEqual([emptyChannel]); }); }); @@ -374,7 +417,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[0].channel; const newMessage = generateMessage({ @@ -403,7 +446,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[0].channel; @@ -465,7 +508,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[0].channel; @@ -527,7 +570,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => { expect(screen.getByTestId('channel-list-view')).toBeTruthy(); }); @@ -568,13 +611,19 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const updatedMessage = { ...channels[0].messages[0] }; updatedMessage.text = uuidv4(); - act(() => dispatchMessageUpdatedEvent(chatClient, updatedMessage, channels[0].channel)); + act(() => + dispatchMessageUpdatedEvent( + chatClient, + updatedMessage as MessageResponse, + channels[0].channel, + ), + ); await waitFor(async () => { const messagesRows = await BetterSqlite.selectFromTable('messages'); @@ -590,7 +639,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const removedChannel = channels[getRandomInt(0, channels.length - 1)].channel; act(() => dispatchNotificationRemovedFromChannel(chatClient, removedChannel)); @@ -621,7 +670,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const removedChannel = channels[getRandomInt(0, channels.length - 1)].channel; act(() => dispatchChannelDeletedEvent(chatClient, removedChannel)); @@ -652,7 +701,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const hiddenChannel = channels[getRandomInt(0, channels.length - 1)].channel; act(() => dispatchChannelHiddenEvent(chatClient, hiddenChannel)); @@ -686,7 +735,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const hiddenChannel = channels[getRandomInt(0, channels.length - 1)].channel; // first, we mark it as hidden @@ -747,7 +796,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const newChannel = createChannel(); @@ -785,7 +834,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelToTruncate = channels[getRandomInt(0, channels.length - 1)].channel; @@ -821,15 +870,19 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelResponse = channels[getRandomInt(0, channels.length - 1)]; const channelToTruncate = channelResponse.channel; const messages = channelResponse.messages; - messages.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()); + messages.sort( + (a: Partial | LocalMessage, b: Partial | LocalMessage) => + new Date(a.created_at as string | Date).getTime() - + new Date(b.created_at as string | Date).getTime(), + ); // truncate at the middle - const truncatedAt = messages[Number(messages.length / 2)].created_at; + const truncatedAt = messages[Number(messages.length / 2)].created_at as string | undefined; act(() => dispatchChannelTruncatedEvent(chatClient, { ...channelToTruncate, @@ -869,7 +922,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelResponse = channels[getRandomInt(0, channels.length - 1)]; @@ -905,13 +958,17 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const channelResponse = channels[getRandomInt(0, channels.length - 1)]; const channelToTruncate = channelResponse.channel; const messages = channelResponse.messages; - const latestTimestamp = Math.max(...messages.map((m) => new Date(m.created_at).getTime())); + const latestTimestamp = Math.max( + ...messages.map((m: Partial | LocalMessage) => + new Date(m.created_at as string | Date).getTime(), + ), + ); // truncate at the middle const truncatedAt = new Date(latestTimestamp + 1).toISOString(); act(() => @@ -943,7 +1000,7 @@ export const Generic = () => { useMockedApis(chatClient, [queryChannelsApi(channels)]); renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -959,14 +1016,14 @@ export const Generic = () => { }); const messageWithNewReaction = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions, newReaction], + latest_reactions: [...(targetMessage.latest_reactions ?? []), newReaction], }; act(() => dispatchReactionNewEvent( chatClient, newReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); @@ -976,7 +1033,7 @@ export const Generic = () => { const matchingReactionsRows = reactionsRows.filter( (r) => r.type === newReaction.type && - r.userId === reactionMember.user.id && + r.userId === reactionMember.user!.id && r.messageId === messageWithNewReaction.id, ); @@ -988,7 +1045,7 @@ export const Generic = () => { useMockedApis(chatClient, [queryChannelsApi(channels)]); renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -997,7 +1054,7 @@ export const Generic = () => { const reactionMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)]; const someOtherMember = targetChannel.members.filter( - (member) => reactionMember.user.id !== member.user.id, + (member: Partial) => reactionMember.user!.id !== member.user!.id, )[getRandomInt(0, targetChannel.members.length - 2)]; const newReactions = [ @@ -1019,7 +1076,7 @@ export const Generic = () => { ]; const messageWithNewReactionBase = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions], + latest_reactions: [...(targetMessage.latest_reactions ?? [])], }; const newLatestReactions: ReactionResponse[] = []; @@ -1027,24 +1084,27 @@ export const Generic = () => { newLatestReactions.push(newReaction); const messageWithNewReaction = { ...messageWithNewReactionBase, - latest_reactions: [...messageWithNewReactionBase.latest_reactions, ...newLatestReactions], + latest_reactions: [ + ...(messageWithNewReactionBase.latest_reactions ?? []), + ...newLatestReactions, + ], }; act(() => dispatchReactionNewEvent( chatClient, newReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); }); const finalReactionCount = - messageWithNewReactionBase.latest_reactions.length + + (messageWithNewReactionBase.latest_reactions ?? []).length + newReactions.filter( (newReaction) => - !messageWithNewReactionBase.latest_reactions.some( - (initialReaction) => + !(messageWithNewReactionBase.latest_reactions ?? []).some( + (initialReaction: ReactionResponse) => initialReaction.type === newReaction.type && initialReaction.user!.id === newReaction.user!.id, ), @@ -1072,7 +1132,7 @@ export const Generic = () => { useMockedApis(chatClient, [queryChannelsApi(channels)]); renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1100,7 +1160,7 @@ export const Generic = () => { ]; const messageWithNewReactionBase = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions], + latest_reactions: [...(targetMessage.latest_reactions ?? [])], }; const newLatestReactions: ReactionResponse[] = []; @@ -1108,13 +1168,16 @@ export const Generic = () => { newLatestReactions.push(newReaction); const messageWithNewReaction = { ...messageWithNewReactionBase, - latest_reactions: [...messageWithNewReactionBase.latest_reactions, ...newLatestReactions], + latest_reactions: [ + ...(messageWithNewReactionBase.latest_reactions ?? []), + ...newLatestReactions, + ], }; act(() => dispatchReactionNewEvent( chatClient, newReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); @@ -1125,7 +1188,7 @@ export const Generic = () => { const matchingReactionsRows = reactionsRows.filter( (r) => r.type === 'wow' && - r.userId === reactionMember.user.id && + r.userId === reactionMember.user!.id && r.messageId === messageWithNewReactionBase.id, ); @@ -1138,13 +1201,13 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)]; - const reactionsOnTargetMessage = targetMessage.latest_reactions; + const reactionsOnTargetMessage = targetMessage.latest_reactions ?? []; const reactionToBeRemoved = reactionsOnTargetMessage[getRandomInt(0, reactionsOnTargetMessage.length - 1)]; @@ -1169,7 +1232,7 @@ export const Generic = () => { dispatchReactionDeletedEvent( chatClient, reactionToBeRemoved, - messageWithoutDeletedReaction, + messageWithoutDeletedReaction as MessageResponse, targetChannel.channel, ), ); @@ -1192,13 +1255,13 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMessage = targetChannel.messages[getRandomInt(0, targetChannel.messages.length - 1)]; - const reactionsOnTargetMessage = targetMessage.latest_reactions; + const reactionsOnTargetMessage = targetMessage.latest_reactions ?? []; const reactionToBeUpdated = reactionsOnTargetMessage[getRandomInt(0, reactionsOnTargetMessage.length - 1)]; reactionToBeUpdated.type = 'wow'; @@ -1207,7 +1270,7 @@ export const Generic = () => { dispatchReactionUpdatedEvent( chatClient, reactionToBeUpdated, - targetMessage, + targetMessage as MessageResponse, targetChannel.channel, ), ); @@ -1229,7 +1292,7 @@ export const Generic = () => { useMockedApis(chatClient, [queryChannelsApi(channels)]); renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1252,7 +1315,7 @@ export const Generic = () => { ]; const messageWithNewReactionBase = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions], + latest_reactions: [...(targetMessage.latest_reactions ?? [])], }; const newLatestReactions: ReactionResponse[] = []; @@ -1260,13 +1323,16 @@ export const Generic = () => { newLatestReactions.push(newReaction); const messageWithNewReaction = { ...messageWithNewReactionBase, - latest_reactions: [...messageWithNewReactionBase.latest_reactions, ...newLatestReactions], + latest_reactions: [ + ...(messageWithNewReactionBase.latest_reactions ?? []), + ...newLatestReactions, + ], }; act(() => dispatchReactionNewEvent( chatClient, newReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); @@ -1276,7 +1342,7 @@ export const Generic = () => { const reactionsRows = await BetterSqlite.selectFromTable('reactions'); const matchingReactionsRows = reactionsRows.filter( (r) => - r.messageId === messageWithNewReactionBase.id && r.userId === reactionMember.user.id, + r.messageId === messageWithNewReactionBase.id && r.userId === reactionMember.user!.id, ); expect(matchingReactionsRows.length).toBe(2); @@ -1297,14 +1363,14 @@ export const Generic = () => { }); const messageWithNewReaction = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions, uniqueReaction], + latest_reactions: [...(targetMessage.latest_reactions ?? []), uniqueReaction], }; act(() => dispatchReactionUpdatedEvent( chatClient, uniqueReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); @@ -1314,7 +1380,7 @@ export const Generic = () => { const matchingReactionsRows = reactionsRows.filter( (r) => r.type === uniqueReaction.type && - r.userId === reactionMember.user.id && + r.userId === reactionMember.user!.id && r.messageId === messageWithNewReaction.id, ); @@ -1326,7 +1392,7 @@ export const Generic = () => { useMockedApis(chatClient, [queryChannelsApi(channels)]); renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1345,7 +1411,7 @@ export const Generic = () => { // anything impossible given the scenarios is fine const messageWithNewReaction = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions, newReaction], + latest_reactions: [...(targetMessage.latest_reactions ?? []), newReaction], reaction_groups: { ...targetMessage.reaction_groups, [newReaction.type]: { @@ -1361,7 +1427,7 @@ export const Generic = () => { dispatchReactionNewEvent( chatClient, newReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); @@ -1385,7 +1451,7 @@ export const Generic = () => { useMockedApis(chatClient, [queryChannelsApi(channels)]); renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1402,7 +1468,7 @@ export const Generic = () => { const newDate = new Date().toISOString(); const messageWithNewReaction = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions, newReaction], + latest_reactions: [...(targetMessage.latest_reactions ?? []), newReaction], reaction_groups: { ...targetMessage.reaction_groups, [newReaction.type]: { @@ -1418,7 +1484,7 @@ export const Generic = () => { dispatchReactionUpdatedEvent( chatClient, newReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); @@ -1442,7 +1508,7 @@ export const Generic = () => { useMockedApis(chatClient, [queryChannelsApi(channels)]); renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1459,7 +1525,7 @@ export const Generic = () => { const newDate = new Date().toISOString(); const messageWithNewReaction = { ...targetMessage, - latest_reactions: [...targetMessage.latest_reactions, newReaction], + latest_reactions: [...(targetMessage.latest_reactions ?? []), newReaction], reaction_groups: { ...targetMessage.reaction_groups, [newReaction.type]: { @@ -1475,7 +1541,7 @@ export const Generic = () => { dispatchReactionDeletedEvent( chatClient, newReaction, - messageWithNewReaction, + messageWithNewReaction as MessageResponse, targetChannel.channel, ), ); @@ -1500,7 +1566,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1528,7 +1594,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1556,7 +1622,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1583,7 +1649,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; @@ -1609,7 +1675,7 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)]; @@ -1620,11 +1686,16 @@ export const Generic = () => { // `last_read` is not on `Event` (the real field is `last_read_at`), but the test fixture // has historically passed `last_read`. Preserve the runtime payload shape exactly and // widen the type at the call site. - dispatchMessageReadEvent(chatClient, targetMember.user, targetChannel.channel, { - first_unread_message_id: '123', - last_read: readTimestamp, - last_read_message_id: '321', - } as unknown as Partial); + dispatchMessageReadEvent( + chatClient, + targetMember.user as UserResponse, + targetChannel.channel, + { + first_unread_message_id: '123', + last_read: readTimestamp, + last_read_message_id: '321', + } as unknown as Partial, + ); }); await waitFor(async () => { @@ -1652,12 +1723,12 @@ export const Generic = () => { renderComponent(); act(() => dispatchConnectionChangedEvent(chatClient)); - await act(async () => await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true)); + await act(async () => await getSyncManager(chatClient).invokeSyncStatusListeners(true)); await waitFor(() => expect(screen.getByTestId('channel-list-view')).toBeTruthy()); const targetChannel = channels[getRandomInt(0, channels.length - 1)]; const targetMember = targetChannel.members[getRandomInt(0, targetChannel.members.length - 1)]; - chatClient.userID = targetMember.user.id; + chatClient.userID = targetMember.user!.id; chatClient.user = targetMember.user; const readTimestamp = new Date().toISOString(); diff --git a/package/src/__tests__/offline-support/optimistic-update.tsx b/package/src/__tests__/offline-support/optimistic-update.tsx index fcb89de2d3..aa12e875be 100644 --- a/package/src/__tests__/offline-support/optimistic-update.tsx +++ b/package/src/__tests__/offline-support/optimistic-update.tsx @@ -4,10 +4,13 @@ import { View } from 'react-native'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; import type { + Channel as ChannelLLC, ChannelAPIResponse, ChannelMemberResponse, LocalMessage, ReactionResponse, + StreamChat, + UserResponse, } from 'stream-chat'; import { v4 as uuidv4 } from 'uuid'; @@ -41,13 +44,40 @@ const Channel = ChannelRaw as unknown as React.ComponentType< React.ComponentProps & { initialValue?: string } >; +// Tests reach into internal / private StreamChat + LLC Channel APIs (sync manager, legacy +// `wsConnection`, `_deleteMessage`, `_sendReaction`, `_sendMessage`). Helpers narrow at the +// call sites without sprinkling `any` everywhere. +type TestPendingTask = { id: number; type: string; payload: unknown }; +type TestSyncManager = { + invokeSyncStatusListeners: (recovered: boolean) => Promise; +}; +// Intentionally not intersected with the real `StreamChat['offlineDb']` — the +// real `syncManager` member is a class with `invokeSyncStatusListeners` marked +// private, which conflicts with the test-only accessor. Kept as a standalone +// test shim shape. +type TestOfflineDb = { + addPendingTask: (task: { + channelId: string | undefined; + channelType: string; + messageId: string; + payload: unknown; + type: string; + }) => Promise; + deletePendingTask: (params: { id: number }) => Promise; + getPendingTasks: () => Promise; + syncManager: TestSyncManager; +}; +const getOfflineDb = (client: StreamChat): TestOfflineDb => + client.offlineDb as unknown as TestOfflineDb; + test('Workaround to allow exporting tests', () => expect(true).toBe(true)); export const OptimisticUpdates = () => { describe('Optimistic Updates', () => { - let chatClient; + let chatClient: StreamChat; - const getRandomInt = (lower, upper) => Math.floor(lower + Math.random() * (upper - lower + 1)); + const getRandomInt = (lower: number, upper: number) => + Math.floor(lower + Math.random() * (upper - lower + 1)); const createChannel = () => { const allUsers = Array(20).fill(1).map(generateUser); const allMessages: LocalMessage[] = []; @@ -63,7 +93,7 @@ export const OptimisticUpdates = () => { const begin = getRandomInt(0, allUsers.length - 2); // begin shouldn't be the end of users.length const end = getRandomInt(begin + 1, allUsers.length - 1); const usersForMembers = allUsers.slice(begin, end); - const members = usersForMembers.map((user) => + const members = usersForMembers.map((user: UserResponse) => generateMember({ user, }), @@ -78,7 +108,7 @@ export const OptimisticUpdates = () => { const end = getRandomInt(begin + 1, usersForMembers.length - 1); const usersForReactions = usersForMembers.slice(begin, end); - const reactions = usersForReactions.map((user) => + const reactions = usersForReactions.map((user: UserResponse) => generateReaction({ message_id: id, user, @@ -94,7 +124,7 @@ export const OptimisticUpdates = () => { }); }); - const reads = members.map((member) => ({ + const reads = members.map((member: ChannelMemberResponse) => ({ last_read: new Date(new Date().setDate(new Date().getDate() - getRandomInt(0, 20))), unread_messages: getRandomInt(0, messages.length), user: member.user, @@ -136,7 +166,10 @@ export const OptimisticUpdates = () => { channels: [channelResponse] as unknown as ChannelAPIResponse[], isLatestMessagesSet: true, }); - chatClient.wsConnection = { isHealthy: true, onlineStatusChanged: jest.fn() }; + chatClient.wsConnection = { + isHealthy: true, + onlineStatusChanged: jest.fn(), + } as unknown as StreamChat['wsConnection']; }); afterEach(() => { @@ -146,11 +179,19 @@ export const OptimisticUpdates = () => { jest.clearAllMocks(); }); - let channel; + let channel: ChannelLLC; // This component is used for performing effects in a component that consumes ChannelContext, // i.e. making use of the callbacks & values provided by the Channel component. // the effect is called every time channelContext changes - const CallbackEffectWithContext = ({ callback, children, context }) => { + const CallbackEffectWithContext = ({ + callback, + children, + context, + }: { + callback: (ctx: T) => Promise | void; + children: React.ReactNode; + context: React.Context; + }) => { const ctx = useContext(context); const [ready, setReady] = useState(false); useEffect(() => { @@ -166,7 +207,7 @@ export const OptimisticUpdates = () => { return null; } - return children; + return <>{children}; }; describe('delete message', () => { @@ -297,7 +338,7 @@ export const OptimisticUpdates = () => { localMessage: newMessage, message: newMessage, options: {}, - }); + } as unknown as Awaited>); render( @@ -340,7 +381,7 @@ export const OptimisticUpdates = () => { { useMockedApis(chatClient, [sendMessageApi(newMessage)]); - await sendMessage({ customMessageData: newMessage }); + await sendMessage(); }} context={MessageInputContext} > @@ -430,8 +471,8 @@ export const OptimisticUpdates = () => { { - await chatClient.offlineDb.addPendingTask({ + (async (_channelId: string, localMessage: LocalMessage, options: unknown) => { + await getOfflineDb(chatClient).addPendingTask({ channelId: channel.id, channelType: channel.type, messageId: message.id, @@ -475,8 +516,8 @@ export const OptimisticUpdates = () => { const dbMessages = await BetterSqlite.selectFromTable('messages'); const dbMessage = dbMessages.find((row) => row.id === message.id); - expect(updatedMessage.text).toBe(editedText); - expect(updatedMessage.message_text_updated_at).toBeTruthy(); + expect(updatedMessage!.text).toBe(editedText); + expect(updatedMessage!.message_text_updated_at).toBeTruthy(); expect(pendingTasksRows).toHaveLength(1); expect(pendingTasksRows[0].type).toBe('update-message'); expect(dbMessage!.text).toBe(editedText); @@ -527,7 +568,7 @@ export const OptimisticUpdates = () => { const dbMessages = await BetterSqlite.selectFromTable('messages'); const dbMessage = dbMessages.find((row) => row.id === message.id); - expect(updatedMessage.text).toBe(editedText); + expect(updatedMessage!.text).toBe(editedText); expect(pendingTasksRows).toHaveLength(0); expect(dbMessage!.text).toBe(editedText); }); @@ -638,8 +679,8 @@ export const OptimisticUpdates = () => { const dbMessage = dbMessages.find((row) => row.id === message.id); const storedAttachments = JSON.parse(dbMessage!.attachments as string); - expect(updatedMessage.text).toBe(editedText); - expect(updatedMessage.attachments[0].asset_url).toBe(localUri); + expect(updatedMessage!.text).toBe(editedText); + expect(updatedMessage!.attachments![0].asset_url).toBe(localUri); expect(pendingTasksRows).toHaveLength(0); expect(dbMessage!.text).toBe(editedText); expect(storedAttachments[0].asset_url).toBe(localUri); @@ -706,7 +747,7 @@ export const OptimisticUpdates = () => { localMessage: newMessage, message: newMessage, options: {}, - }); + } as unknown as Awaited>); // initialValue is needed as a prop to trick the message input ctx into thinking // we are sending a message. @@ -760,11 +801,11 @@ export const OptimisticUpdates = () => { user_id: chatClient.userID, }); - jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({ - localMessage, - message: localMessage, - options: {}, - }); + jest + .spyOn(channel.messageComposer, 'compose') + .mockResolvedValue({ localMessage, message: localMessage } as unknown as Awaited< + ReturnType + >); render( @@ -783,23 +824,25 @@ export const OptimisticUpdates = () => { ); await waitFor(() => expect(screen.getByTestId('children')).toBeTruthy()); - let pendingTask; + let pendingTask: TestPendingTask | undefined; await waitFor(async () => { - const pendingTasks = await chatClient.offlineDb.getPendingTasks(); + const pendingTasks = await getOfflineDb(chatClient).getPendingTasks(); expect(pendingTasks).toHaveLength(1); pendingTask = pendingTasks[0]; }); expect(channel.state.messages.some((message) => message.id === localMessage.id)).toBe(true); - jest.spyOn(channel, 'watch').mockResolvedValue({}); + jest + .spyOn(channel, 'watch') + .mockResolvedValue({} as Awaited>); channel.state.removeMessage(localMessage); channel.state.addMessageSorted(serverMessage, true); - await chatClient.offlineDb.deletePendingTask({ id: pendingTask.id }); + await getOfflineDb(chatClient).deletePendingTask({ id: pendingTask!.id }); await act(async () => { - await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true); + await getOfflineDb(chatClient).syncManager.invokeSyncStatusListeners(true); }); await waitFor(() => { @@ -821,11 +864,11 @@ export const OptimisticUpdates = () => { user_id: chatClient.userID, }); - jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({ - localMessage, - message: localMessage, - options: {}, - }); + jest + .spyOn(channel.messageComposer, 'compose') + .mockResolvedValue({ localMessage, message: localMessage } as unknown as Awaited< + ReturnType + >); render( @@ -844,20 +887,22 @@ export const OptimisticUpdates = () => { ); await waitFor(() => expect(screen.getByTestId('children')).toBeTruthy()); - let pendingTask; + let pendingTask: TestPendingTask | undefined; await waitFor(async () => { - const pendingTasks = await chatClient.offlineDb.getPendingTasks(); + const pendingTasks = await getOfflineDb(chatClient).getPendingTasks(); expect(pendingTasks).toHaveLength(1); pendingTask = pendingTasks[0]; }); - jest.spyOn(channel, 'watch').mockResolvedValue({}); + jest + .spyOn(channel, 'watch') + .mockResolvedValue({} as Awaited>); channel.state.removeMessage(localMessage); - await chatClient.offlineDb.deletePendingTask({ id: pendingTask.id }); + await getOfflineDb(chatClient).deletePendingTask({ id: pendingTask!.id }); await act(async () => { - await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true); + await getOfflineDb(chatClient).syncManager.invokeSyncStatusListeners(true); }); await waitFor(() => { diff --git a/package/src/components/Attachment/__tests__/Giphy.test.tsx b/package/src/components/Attachment/__tests__/Giphy.test.tsx index f9d394393b..1fdc4128ec 100644 --- a/package/src/components/Attachment/__tests__/Giphy.test.tsx +++ b/package/src/components/Attachment/__tests__/Giphy.test.tsx @@ -195,14 +195,23 @@ describe('Giphy', () => { attachment.giphy = giphy; render(getAttachmentComponent({ attachment, giphyVersion: 'fixed_height' })); await waitFor(() => { - const checkImageProps = (imageProps, specificSizedGiphyData) => { - let imageStyle = imageProps.style; + const checkImageProps = ( + imageProps: Record, + specificSizedGiphyData: { height: string; url: string; width: string }, + ) => { + let imageStyle = imageProps.style as + | Record + | Array>; if (Array.isArray(imageStyle)) { imageStyle = Object.assign({}, ...imageStyle); } - expect(imageStyle.height).toBe(parseFloat(specificSizedGiphyData.height)); - expect(imageStyle.width).toBe(parseFloat(specificSizedGiphyData.width)); - expect(imageProps.source.uri).toBe(specificSizedGiphyData.url); + expect((imageStyle as Record).height).toBe( + parseFloat(specificSizedGiphyData.height), + ); + expect((imageStyle as Record).width).toBe( + parseFloat(specificSizedGiphyData.width), + ); + expect((imageProps.source as { uri: string }).uri).toBe(specificSizedGiphyData.url); }; checkImageProps( screen.getByLabelText('Giphy Attachment Image').props, @@ -211,14 +220,23 @@ describe('Giphy', () => { }); render(getAttachmentComponent({ attachment, giphyVersion: 'original' })); await waitFor(() => { - const checkImageProps = (imageProps, specificSizedGiphyData) => { - let imageStyle = imageProps.style; + const checkImageProps = ( + imageProps: Record, + specificSizedGiphyData: { height: string; url: string; width: string }, + ) => { + let imageStyle = imageProps.style as + | Record + | Array>; if (Array.isArray(imageStyle)) { imageStyle = Object.assign({}, ...imageStyle); } - expect(imageStyle.height).toBe(parseFloat(specificSizedGiphyData.height)); - expect(imageStyle.width).toBe(parseFloat(specificSizedGiphyData.width)); - expect(imageProps.source.uri).toBe(specificSizedGiphyData.url); + expect((imageStyle as Record).height).toBe( + parseFloat(specificSizedGiphyData.height), + ); + expect((imageStyle as Record).width).toBe( + parseFloat(specificSizedGiphyData.width), + ); + expect((imageProps.source as { uri: string }).uri).toBe(specificSizedGiphyData.url); }; checkImageProps( screen.getByLabelText('Giphy Attachment Image').props, diff --git a/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx b/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx index 879a798f51..8ca4144379 100644 --- a/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx +++ b/package/src/components/AutoCompleteInput/__tests__/AutoCompleteInput.test.tsx @@ -1,18 +1,28 @@ import React from 'react'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; +import type { ChannelProps } from '../../Channel/Channel'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { AutoCompleteInput } from '../AutoCompleteInput'; -const renderComponent = ({ channelProps, client, props }) => { +const renderComponent = ({ + channelProps, + client, + props, +}: { + channelProps: Partial; + client: StreamChat; + props: React.ComponentProps; +}) => { return render( - + @@ -21,8 +31,8 @@ const renderComponent = ({ channelProps, client, props }) => { }; describe('AutoCompleteInput', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); @@ -70,7 +80,7 @@ describe('AutoCompleteInput', () => { it('should have the maxLength same as the one on the config of channel', async () => { jest.spyOn(channel, 'getConfig').mockReturnValue({ max_message_length: 10, - }); + } as unknown as ReturnType); const channelProps = { channel }; const props = {}; diff --git a/package/src/components/Chat/__tests__/Chat.test.tsx b/package/src/components/Chat/__tests__/Chat.test.tsx index 83cc845365..9f382c0564 100644 --- a/package/src/components/Chat/__tests__/Chat.test.tsx +++ b/package/src/components/Chat/__tests__/Chat.test.tsx @@ -5,8 +5,10 @@ import NetInfo from '@react-native-community/netinfo'; import { act, cleanup, render, waitFor } from '@testing-library/react-native'; +import type { ChatContextValue } from '../../../contexts/chatContext/ChatContext'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; +import type { TranslationContextValue } from '../../../contexts/translationContext/TranslationContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import dispatchConnectionChangedEvent from '../../../mock-builders/event/connectionChanged'; import dispatchConnectionRecoveredEvent from '../../../mock-builders/event/connectionRecovered'; @@ -14,12 +16,12 @@ import { getTestClient, getTestClientWithUser, setUser } from '../../../mock-bui import { Streami18n } from '../../../utils/i18n/Streami18n'; import { Chat } from '../Chat'; -const ChatContextConsumer = ({ fn }) => { +const ChatContextConsumer = ({ fn }: { fn: (ctx: ChatContextValue) => void }) => { fn(useChatContext()); return ; }; -const TranslationContextConsumer = ({ fn }) => { +const TranslationContextConsumer = ({ fn }: { fn: (ctx: TranslationContextValue) => void }) => { fn(useTranslationContext()); return ; }; @@ -42,7 +44,7 @@ describe('Chat', () => { }); it('listens and updates state on a connection changed event', async () => { - let context; + let context: ChatContextValue = {} as ChatContextValue; render( @@ -65,7 +67,7 @@ describe('Chat', () => { }); it('listens and updates state on a connection recovered event', async () => { - let context; + let context: ChatContextValue = {} as ChatContextValue; render( @@ -87,7 +89,7 @@ describe('ChatContext', () => { afterEach(cleanup); const chatClient = getTestClient(); it('exposes the chat context', async () => { - let context; + let context: ChatContextValue = {} as ChatContextValue; render( @@ -109,7 +111,7 @@ describe('ChatContext', () => { }); it('calls setActiveChannel to set a new channel in context', async () => { - let context; + let context: ChatContextValue = {} as ChatContextValue; render( @@ -124,7 +126,11 @@ describe('ChatContext', () => { const channel = { cid: 'cid', id: 'cid', query: jest.fn() }; await waitFor(() => expect(context.channel).toBeUndefined()); - act(() => context.setActiveChannel(channel)); + act(() => + context.setActiveChannel( + channel as unknown as Parameters[0], + ), + ); await waitFor(() => expect(context.channel).toStrictEqual(channel)); }); @@ -138,7 +144,7 @@ describe('TranslationContext', () => { const chatClient = getTestClient(); it('exposes the translation context', async () => { - let context; + let context: TranslationContextValue = {} as TranslationContextValue; render( @@ -158,7 +164,7 @@ describe('TranslationContext', () => { }); it('uses the i18nInstance provided in props', async () => { - let context; + let context: TranslationContextValue = {} as TranslationContextValue; const i18nInstance = new Streami18n(); const { t, tDateTimeParser } = await i18nInstance.getTranslators(); @@ -185,7 +191,7 @@ describe('TranslationContext', () => { }); it('updates the context when props change', async () => { - let context; + let context: TranslationContextValue = {} as TranslationContextValue; const i18nInstance = new Streami18n(); i18nInstance.t = (() => 't') as unknown as typeof i18nInstance.t; @@ -236,8 +242,8 @@ describe('TranslationContext', () => { // initial mount and render const { rerender } = render(); - let unsubscribeSpy; - let listenersAfterInitialMount; + let unsubscribeSpy: jest.SpyInstance | undefined; + let listenersAfterInitialMount: Array = []; const initSpy = jest.spyOn(chatClientWithUser.offlineDb!.syncManager, 'init'); await waitFor(() => { @@ -267,8 +273,8 @@ describe('TranslationContext', () => { // initial render const { rerender } = render(); - let unsubscribeSpy; - let listenersAfterInitialMount; + let unsubscribeSpy: jest.SpyInstance | undefined; + let listenersAfterInitialMount: Array = []; const initSpy = jest.spyOn(chatClientWithUser.offlineDb!.syncManager, 'init'); await waitFor(() => { @@ -302,7 +308,7 @@ describe('TranslationContext', () => { // initial render const { rerender } = render(); - let unsubscribeSpy; + let unsubscribeSpy: jest.SpyInstance | undefined; const initSpy = jest.spyOn(chatClientWithUser.offlineDb!.syncManager, 'init'); await waitFor(() => { diff --git a/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx b/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx index 6023a1238b..8c42546e19 100644 --- a/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/Message.test.tsx @@ -4,7 +4,9 @@ import { Pressable, Text, View } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { cleanup, fireEvent, render, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; +import type { ComponentOverrides } from '../../../../contexts/componentsContext/ComponentsContext'; import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; import { useMessageContext } from '../../../../contexts/messageContext/MessageContext'; import { MessageListItemProvider } from '../../../../contexts/messageListItemContext/MessageListItemContext'; @@ -24,7 +26,7 @@ import { useShouldUseOverlayStyles } from '../../hooks/useShouldUseOverlayStyles import { Message } from '../../Message'; import { MessageOverlayWrapper } from '../../MessageOverlayWrapper'; -const OverlayStateText = ({ label }) => { +const OverlayStateText = ({ label }: { label: string }) => { const shouldUseOverlayStyles = useShouldUseOverlayStyles(); return {`${label}:${shouldUseOverlayStyles ? 'overlay' : 'normal'}`}; @@ -54,9 +56,14 @@ const CustomMessageItemView = () => ( ); describe('Message', () => { - let channel; - let chatClient; - let renderMessage; + let channel: ChannelType; + let chatClient: StreamChat; + let renderMessage: ( + options: Omit, 'groupStyles'> & + Partial, 'groupStyles'>>, + channelProps?: Partial>, + componentOverrides?: ComponentOverrides, + ) => ReturnType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ user })]; diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx index 19802c578f..c0b4c4ce5a 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx @@ -117,7 +117,9 @@ describe('MessageContent', () => { const user = generateUser(); const message = generateMessage({ user }); - const ContextMessageHeader = (props) => ; + const ContextMessageHeader = (props: Record) => ( + + ); render( @@ -141,7 +143,9 @@ describe('MessageContent', () => { const user = generateUser(); const message = generateMessage({ user }); - const ContextMessageFooter = (props) => ; + const ContextMessageFooter = (props: Record) => ( + + ); render( diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx index 1fa2d36ab3..2f0ef73c7e 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx @@ -4,8 +4,10 @@ import { StyleSheet, Text } from 'react-native'; import { GestureDetector } from 'react-native-gesture-handler'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; +import type { ComponentOverrides } from '../../../../contexts/componentsContext/ComponentsContext'; import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext'; import { useMessageContext } from '../../../../contexts/messageContext/MessageContext'; @@ -25,9 +27,14 @@ import { Chat } from '../../../Chat/Chat'; import { Message } from '../../Message'; describe('MessageItemView', () => { - let channel; - let chatClient; - let renderMessage; + let channel: ChannelType; + let chatClient: StreamChat; + let renderMessage: ( + options: Omit, 'groupStyles'> & + Partial, 'groupStyles'>>, + channelProps?: Partial>, + componentOverrides?: ComponentOverrides, + ) => ReturnType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ user })]; diff --git a/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx b/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx index e2c19e1f28..6ff6d39dae 100644 --- a/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/ReactionListBottom.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; @@ -17,9 +18,13 @@ import { Chat } from '../../../Chat/Chat'; import { Message } from '../../Message'; describe('ReactionListBottom', () => { - let channel; - let chatClient; - let renderMessage; + let channel: ChannelType; + let chatClient: StreamChat; + let renderMessage: ( + options: Omit, 'groupStyles'> & + Partial, 'groupStyles'>>, + channelProps?: Partial>, + ) => ReturnType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ user })]; @@ -159,7 +164,7 @@ describe('ReactionListBottom', () => { { handleReaction: handleReactionMock, message, - }, + } as unknown as React.ComponentProps, { reactionListPosition: 'bottom', reactionListType: 'segmented' }, ); diff --git a/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx b/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx index c1f34cbaf4..344e2489e7 100644 --- a/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/ReactionListTop.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext'; @@ -16,9 +17,12 @@ import { Chat } from '../../../Chat/Chat'; import { ReactionListTop } from '../ReactionList/ReactionListTop'; describe('ReactionListTop', () => { - let channel; - let chatClient; - let renderMessage; + let channel: ChannelType; + let chatClient: StreamChat; + let renderMessage: ( + options: React.ComponentProps, + channelProps?: Partial>, + ) => ReturnType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ user })]; diff --git a/package/src/components/MessageInput/__tests__/AttachButton.test.tsx b/package/src/components/MessageInput/__tests__/AttachButton.test.tsx index a4198f36f5..a28203cdcf 100644 --- a/package/src/components/MessageInput/__tests__/AttachButton.test.tsx +++ b/package/src/components/MessageInput/__tests__/AttachButton.test.tsx @@ -1,20 +1,30 @@ import React from 'react'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; import * as NativeHandler from '../../../native'; +import type { ChannelProps } from '../../Channel/Channel'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { AttachButton } from '../components/InputButtons/AttachButton'; -const renderComponent = ({ channelProps, client, props }) => { +const renderComponent = ({ + channelProps, + client, + props, +}: { + channelProps: Partial; + client: StreamChat; + props: React.ComponentProps; +}) => { return render( - + @@ -23,8 +33,8 @@ const renderComponent = ({ channelProps, client, props }) => { }; describe('AttachButton', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); diff --git a/package/src/components/MessageInput/__tests__/InputButtons.test.tsx b/package/src/components/MessageInput/__tests__/InputButtons.test.tsx index d25e38492d..8b5066a4f6 100644 --- a/package/src/components/MessageInput/__tests__/InputButtons.test.tsx +++ b/package/src/components/MessageInput/__tests__/InputButtons.test.tsx @@ -1,19 +1,29 @@ import React from 'react'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { OverlayProvider } from '../../../contexts'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; +import type { ChannelProps } from '../../Channel/Channel'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { InputButtons } from '../components/InputButtons/index'; -const renderComponent = ({ channelProps, client, props }) => { +const renderComponent = ({ + channelProps, + client, + props, +}: { + channelProps: Partial; + client: StreamChat; + props: React.ComponentProps; +}) => { return render( - + @@ -22,8 +32,8 @@ const renderComponent = ({ channelProps, client, props }) => { }; describe('InputButtons', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); diff --git a/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx b/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx index dfaaccaeaf..2b08d012b7 100644 --- a/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx +++ b/package/src/components/MessageInput/__tests__/MessageComposer.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Alert } from 'react-native'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import * as AttachmentPickerUtils from '../../../contexts/attachmentPickerContext/AttachmentPickerContext'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; @@ -12,6 +13,7 @@ import { initiateClientWithChannels } from '../../../mock-builders/api/initiateC import { AttachmentPickerStore } from '../../../state-store/attachment-picker-store'; import { AttachmentPickerContent } from '../../AttachmentPicker/components/AttachmentPickerContent'; import { AttachmentPickerSelectionBar } from '../../AttachmentPicker/components/AttachmentPickerSelectionBar'; +import type { ChannelProps } from '../../Channel/Channel'; import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { MessageComposer } from '../MessageComposer'; @@ -31,11 +33,19 @@ jest.spyOn(AttachmentPickerUtils, 'useAttachmentPickerContext').mockImplementati } as unknown as ReturnType; }); -const renderComponent = ({ channelProps, client, props }) => { +const renderComponent = ({ + channelProps, + client, + props, +}: { + channelProps: Partial; + client: StreamChat; + props: React.ComponentProps; +}) => { return render( - + @@ -44,8 +54,8 @@ const renderComponent = ({ channelProps, client, props }) => { }; describe('MessageComposer', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { jest.clearAllMocks(); diff --git a/package/src/components/MessageInput/__tests__/SendButton.test.tsx b/package/src/components/MessageInput/__tests__/SendButton.test.tsx index f237aad828..ba6dc987ca 100644 --- a/package/src/components/MessageInput/__tests__/SendButton.test.tsx +++ b/package/src/components/MessageInput/__tests__/SendButton.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { Channel as ChannelType, StreamChat } from 'stream-chat'; import { OverlayProvider } from '../../../contexts'; @@ -9,12 +10,20 @@ import { Channel } from '../../Channel/Channel'; import { Chat } from '../../Chat/Chat'; import { SendButton } from '../components/OutputButtons/SendButton'; -const renderComponent = ({ client, channel, props }) => { +const renderComponent = ({ + client, + channel, + props, +}: { + channel: ChannelType; + client: StreamChat; + props: Partial>; +}) => { return render( - + )} /> , @@ -22,8 +31,8 @@ const renderComponent = ({ client, channel, props }) => { }; describe('SendButton', () => { - let client; - let channel; + let client: StreamChat; + let channel: ChannelType; beforeEach(async () => { const { client: chatClient, channels } = await initiateClientWithChannels(); diff --git a/package/src/components/MessageList/__tests__/MessageList.test.tsx b/package/src/components/MessageList/__tests__/MessageList.test.tsx index de9dc1dd5a..de0cf806fe 100644 --- a/package/src/components/MessageList/__tests__/MessageList.test.tsx +++ b/package/src/components/MessageList/__tests__/MessageList.test.tsx @@ -466,7 +466,9 @@ describe('MessageList pagination', () => { jest.clearAllMocks(); }); - const mockedHook = (values) => { + const mockedHook = ( + values: Partial>, + ) => { const messages = Array.from({ length: 100 }, (_, i) => generateMessage({ text: `message-${i}` }), ); diff --git a/package/src/components/MessageMenu/__tests__/MessageReactionPicker.test.tsx b/package/src/components/MessageMenu/__tests__/MessageReactionPicker.test.tsx index eb324776c5..f694466531 100644 --- a/package/src/components/MessageMenu/__tests__/MessageReactionPicker.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageReactionPicker.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { fireEvent, render, cleanup, waitFor } from '@testing-library/react-native'; +import type { StreamChat } from 'stream-chat'; import { MessageContextValue, @@ -39,8 +40,11 @@ const defaultProps = { }; describe('MessageReactionPicker', () => { - let client; - let renderComponent; + let client: StreamChat; + let renderComponent: ( + props?: Partial>, + ownCapabilities?: Partial, + ) => ReturnType; beforeEach(async () => { client = await getTestClientWithUser({ id: 'reaction-test-user' }); diff --git a/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx b/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx index f36fa82753..00ab96f4ab 100644 --- a/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/filePickers.test.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { Alert } from 'react-native'; import { cleanup, renderHook, waitFor } from '@testing-library/react-native'; +import type { Channel, StreamChat } from 'stream-chat'; import { Chat } from '../../../components'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; @@ -20,7 +21,15 @@ import { jest.spyOn(Alert, 'alert'); -const Wrapper = ({ channel, client, props }) => { +const Wrapper = ({ + channel, + client, + props, +}: { + channel: Channel; + client: StreamChat; + props: PropsWithChildren>; +}) => { return ( { }; describe("MessageInputContext's pickFile", () => { - let channel; - let chatClient; + let channel: Channel; + let chatClient: StreamChat; beforeEach(async () => { const { client, channels } = await initiateClientWithChannels(); @@ -140,8 +149,8 @@ describe("MessageInputContext's pickFile", () => { }); describe("MessageInputContext's pickAndUploadImageFromNativePicker", () => { - let channel; - let chatClient; + let channel: Channel; + let chatClient: StreamChat; beforeEach(async () => { const { client, channels } = await initiateClientWithChannels(); @@ -280,8 +289,8 @@ describe("MessageInputContext's pickAndUploadImageFromNativePicker", () => { }); describe("MessageInputContext's takeAndUploadImage", () => { - let channel; - let chatClient; + let channel: Channel; + let chatClient: StreamChat; beforeEach(async () => { const { client, channels } = await initiateClientWithChannels(); diff --git a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx index fd5d332c28..0206ef1c0a 100644 --- a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { act, cleanup, renderHook, waitFor } from '@testing-library/react-native'; +import type { Channel, StreamChat } from 'stream-chat'; import { Chat } from '../../../components'; import { initiateClientWithChannels } from '../../../mock-builders/api/initiateClientWithChannels'; @@ -10,6 +11,7 @@ import { generateMessage } from '../../../mock-builders/generator/message'; import * as UseMessageComposerAPIContext from '../../messageComposerContext/MessageComposerAPIContext'; import { MessageComposerAPIContextValue } from '../../messageComposerContext/MessageComposerAPIContext'; +import type { MessageComposerContextValue } from '../../messageComposerContext/MessageComposerContext'; import { MessageComposerProvider } from '../../messageComposerContext/MessageComposerContext'; import { OwnCapabilitiesContextValue, @@ -21,11 +23,19 @@ import { useMessageInputContext, } from '../MessageInputContext'; -const Wrapper = ({ messageComposerContextValue, client, props }) => { +const Wrapper = ({ + messageComposerContextValue, + client, + props, +}: { + client: StreamChat; + messageComposerContextValue: Partial; + props: PropsWithChildren>; +}) => { return ( - + { }; describe("MessageInputContext's sendMessage", () => { - let channel; - let chatClient; + let channel: Channel; + let chatClient: StreamChat; beforeEach(async () => { const { client, channels } = await initiateClientWithChannels(); @@ -136,7 +146,11 @@ describe("MessageInputContext's sendMessage", () => { sendMessage: sendMessageMock, }; const { pollComposer } = channel.messageComposer; - jest.spyOn(chatClient, 'createPoll').mockResolvedValue({ poll: { id: 'test-poll-id' } }); + jest + .spyOn(chatClient, 'createPoll') + .mockResolvedValue({ poll: { id: 'test-poll-id' } } as unknown as Awaited< + ReturnType + >); const { result } = renderHook(() => useMessageInputContext(), { initialProps, @@ -157,7 +171,7 @@ describe("MessageInputContext's sendMessage", () => { { id: 1, text: '1' }, { id: 2, text: '2' }, ], - }); + } as unknown as Parameters[0]); await channel.messageComposer.createPoll(); }); @@ -212,8 +226,8 @@ describe("MessageInputContext's sendMessage", () => { }); describe("MessageInputContext's editMessage", () => { - let channel; - let chatClient; + let channel: Channel; + let chatClient: StreamChat; beforeAll(async () => { const { client, channels } = await initiateClientWithChannels(); diff --git a/package/src/state-store/__tests__/audio-player.test.ts b/package/src/state-store/__tests__/audio-player.test.ts index 8321a7674f..7b6c4825a8 100644 --- a/package/src/state-store/__tests__/audio-player.test.ts +++ b/package/src/state-store/__tests__/audio-player.test.ts @@ -190,7 +190,9 @@ describe('AudioPlayer', () => { it('updates playback state from the native playback callback', async () => { const playerRef = createMockNativePlayerRef(); - let onPlaybackStatusUpdate; + let onPlaybackStatusUpdate: ( + status: ReturnType, + ) => unknown = () => undefined; (NativeHandlers as { Sound: unknown }).Sound = { Player: null, initializeSound: jest.fn().mockImplementation((_source, _initialStatus, callback) => { diff --git a/package/src/utils/__tests__/Streami18n.test.ts b/package/src/utils/__tests__/Streami18n.test.ts index faad01f75c..8287508e39 100644 --- a/package/src/utils/__tests__/Streami18n.test.ts +++ b/package/src/utils/__tests__/Streami18n.test.ts @@ -50,7 +50,7 @@ describe('Streami18n instance - with built-in language', () => { it('should provide dutch translator', async () => { const { t: _t } = await streami18n.getTranslators(); for (const key in nlTranslations) { - const value = nlTranslations[key]; + const value = nlTranslations[key as keyof typeof nlTranslations]; const hasTemplateInKey = key.indexOf('{{') > -1 && key.indexOf('}}') > -1; const hasTemplateInValue = typeof value === 'string' && value.indexOf('{{') > -1 && value.indexOf('}}') > -1; @@ -58,7 +58,7 @@ describe('Streami18n instance - with built-in language', () => { continue; } - expect(_t(key)).toBe(nlTranslations[key]); + expect(_t(key)).toBe(nlTranslations[key as keyof typeof nlTranslations]); } }); it('should provide dayjs with `nl` locale', async () => { @@ -78,7 +78,7 @@ describe('Streami18n instance - with built-in language', () => { it('should provide dutch translator', async () => { const { t: _t } = await streami18n.getTranslators(); for (const key in nlTranslations) { - const value = nlTranslations[key]; + const value = nlTranslations[key as keyof typeof nlTranslations]; const hasTemplateInKey = key.indexOf('{{') > -1 && key.indexOf('}}') > -1; const hasTemplateInValue = typeof value === 'string' && value.indexOf('{{') > -1 && value.indexOf('}}') > -1; @@ -86,7 +86,7 @@ describe('Streami18n instance - with built-in language', () => { continue; } - expect(_t(key)).toBe(nlTranslations[key]); + expect(_t(key)).toBe(nlTranslations[key as keyof typeof nlTranslations]); } }); @@ -219,7 +219,7 @@ describe('setLanguage - switch to french', () => { const { t: _t } = await streami18n.getTranslators(); for (const key in frTranslations) { // Skip keys with template strings or duration keys - const value = frTranslations[key]; + const value = frTranslations[key as keyof typeof frTranslations]; const hasTemplateInKey = key.indexOf('{{') > -1 && key.indexOf('}}') > -1; const hasTemplateInValue = typeof value === 'string' && value.indexOf('{{') > -1 && value.indexOf('}}') > -1; @@ -227,7 +227,7 @@ describe('setLanguage - switch to french', () => { continue; } - expect(_t(key)).toBe(frTranslations[key]); + expect(_t(key)).toBe(frTranslations[key as keyof typeof frTranslations]); } }); }); diff --git a/package/src/utils/__tests__/utils.test.ts b/package/src/utils/__tests__/utils.test.ts index fd41bbcd78..0c5e75e93f 100644 --- a/package/src/utils/__tests__/utils.test.ts +++ b/package/src/utils/__tests__/utils.test.ts @@ -1,7 +1,7 @@ import { formatMsToMinSec, getUrlWithoutParams } from '../utils'; describe('getUrlWithoutParams', () => { - const testUrlMap = { + const testUrlMap: Record = { 'http://foo.com/blah_(wikipedia)#cite-1': 'http://foo.com/blah_(wikipedia)#cite-1', 'https://us-east.stream-io-cdn.com/102401/images/418dc024-b587-48cd-84fb-252418e14391.FB_IMG_1633228094526.jpg?Key-Pair-Id=APKAIHG36VEWPDULE23Q&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly91cy1lYXN0LnN0cmVhbS1pby1jZG4uY29tLzEwMjQwMS9pbWFnZXMvNDE4ZGMwMjQtYjU4Ny00OGNkLTg0ZmItMjUyNDE4ZTE0MzkxLkZCX0lNR18xNjMzMjI4MDk0NTI2LmpwZz9jcm9wPSomaD0qJnJlc2l6ZT0qJnJvPTAmdz0qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNjM1MTUwMDM5fX19XX0_&Signature=Yi8XTsAVYiEh2IDSkH4IK1zNEvPvgUkfYx9oJb2VrJMMVrBz2oPurbcFOHuQSk74RQTSE6LPZ-wplayHZxaSVeX4Q6IwwjE7vmnU~-UYPttxnClpRWFUKLJx79auz5sjkhwFte7uzby7oQSRRDRl3g3ritN~NRzU4cjZ0tnLFnn0AwnLDmfEk8VdjgGXm84PeqpAUujyDmSqm1TY7QJQBRnJMQ-MV7AA3Gj8ec9yxWunIOK8xn5FJTRvKAVqEcu~lnmEAMS5RXQ5oDCjp2~w7M7sNSyqgJVe7jRJ0kctRqJeOPlsDfQJB38JwLv6v-5piSt2kTYsPBXUu4EiALwVaQ__&crop=*&h=*&resize=*&ro=0&w=*': 'https://us-east.stream-io-cdn.com/102401/images/418dc024-b587-48cd-84fb-252418e14391.FB_IMG_1633228094526.jpg', diff --git a/package/tsconfig.test.json b/package/tsconfig.test.json index ced9a158c2..bec743aba0 100644 --- a/package/tsconfig.test.json +++ b/package/tsconfig.test.json @@ -2,11 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "noUnusedLocals": false, - "noUnusedParameters": false, - // TODO: flip to `true` after annotating the ~630 remaining implicit-any - // errors in test files (mostly destructured-parameter and `let`-variable - // annotations). See follow-up issue. - "noImplicitAny": false + "noUnusedParameters": false }, "include": ["./src/**/*"], "exclude": [ From 7642d2472222da9fc0497efa087cf66d6b388e73 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 20 Apr 2026 21:13:35 +0200 Subject: [PATCH 14/15] refactor: replace Record with specific types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cuts `Record` uses from 57 → 22. The remaining 22 are legitimate: generic parameter defaults on public API types (`CustomLocalMetadata`), the `UnknownType` export, `Message.additionalInfo` (genuine bag-of-data prop), `useStateStore` / `usePollStateStore` generic constraints, `topologicalResolution` plain-object type guards, `BetterSqlite.selectFromTable` default row type, the i18n formatter `options` (matches i18next API shape), and the one remaining `exception_fields` field on `mock-builders/api/error.ts` which matches the real Stream API error shape. **Source / mock-builders:** - mock-builders/api/initiateClientWithChannels.ts: alias `ChannelData` as `Parameters[0]`. - mock-builders/generator/channel.ts: drop unused `_client` field (only ever set to `{}`, no consumers). - mock-builders/attachments.ts: narrow override arg to `Partial }>` (both callers pass no overrides anyway). - mock-builders/mock.ts: replace two `as unknown as Record` + `as never` casts with a single `type WithPrivates` cast for `_setToken` / `_setupConnection` spies. - components/UIComponents/SwipableWrapper.tsx: `Content` prop typed as bare `React.ComponentType` (rendered as `` with no props). **Tests:** - Channel.test.tsx: 9 uses replaced — `RenderComponentProps` becomes `Partial>` (with `channel?: unknown` to allow the one test that passes an ad-hoc shape); context-cast sites use `typeof mockContext` so test mocks can mix fields from `ChatContextValue` + `MessagesContextValue`; `T extends object` generic; `typeof channel.state.messages`; `Partial>`; `typeof channel.state.read`. - Giphy.test.tsx: 10 uses replaced with `ComponentProps` + `StyleProp` / `ImageStyle` from `react-native`. - ChannelList.test.tsx: `filters` arg typed as `Parameters[0]`. - MessageContent.test.tsx: override-component prop types switched to `MessageHeaderProps` / `MessageFooterProps` from the component sources. - useMessageListPagination.test.tsx: `mockedHook` args typed as `Partial` and `Partial>`. - ChannelDetailsBottomSheet.test.tsx: 3 uses replaced with `ComponentProps`. - ChannelSwipableWrapper.test.tsx: `ComponentProps`. - ChannelPreview.test.tsx: override-map types from `Parameters[0]` and `Partial`. test:typecheck: 0 errors. Full test suite: same pre-existing SQLite-isolation flake; no regressions. --- .../Attachment/__tests__/Giphy.test.tsx | 29 ++++++----------- .../Channel/__tests__/Channel.test.tsx | 31 ++++++++++--------- .../useMessageListPagination.test.tsx | 5 ++- .../__tests__/ChannelList.test.tsx | 2 +- .../ChannelDetailsBottomSheet.test.tsx | 13 +++++--- .../__tests__/ChannelPreview.test.tsx | 6 ++-- .../__tests__/ChannelSwipableWrapper.test.tsx | 5 +-- .../__tests__/MessageContent.test.tsx | 6 ++-- .../UIComponents/SwipableWrapper.tsx | 2 +- .../api/initiateClientWithChannels.ts | 2 +- package/src/mock-builders/attachments.ts | 2 +- .../src/mock-builders/generator/channel.ts | 2 -- package/src/mock-builders/mock.ts | 8 ++--- 13 files changed, 56 insertions(+), 57 deletions(-) diff --git a/package/src/components/Attachment/__tests__/Giphy.test.tsx b/package/src/components/Attachment/__tests__/Giphy.test.tsx index 1fdc4128ec..fc4b14736b 100644 --- a/package/src/components/Attachment/__tests__/Giphy.test.tsx +++ b/package/src/components/Attachment/__tests__/Giphy.test.tsx @@ -1,4 +1,5 @@ import React, { ComponentProps } from 'react'; +import type { Image, ImageStyle, StyleProp } from 'react-native'; import { act, @@ -196,21 +197,15 @@ describe('Giphy', () => { render(getAttachmentComponent({ attachment, giphyVersion: 'fixed_height' })); await waitFor(() => { const checkImageProps = ( - imageProps: Record, + imageProps: ComponentProps, specificSizedGiphyData: { height: string; url: string; width: string }, ) => { - let imageStyle = imageProps.style as - | Record - | Array>; + let imageStyle = imageProps.style as StyleProp; if (Array.isArray(imageStyle)) { imageStyle = Object.assign({}, ...imageStyle); } - expect((imageStyle as Record).height).toBe( - parseFloat(specificSizedGiphyData.height), - ); - expect((imageStyle as Record).width).toBe( - parseFloat(specificSizedGiphyData.width), - ); + expect((imageStyle as ImageStyle).height).toBe(parseFloat(specificSizedGiphyData.height)); + expect((imageStyle as ImageStyle).width).toBe(parseFloat(specificSizedGiphyData.width)); expect((imageProps.source as { uri: string }).uri).toBe(specificSizedGiphyData.url); }; checkImageProps( @@ -221,21 +216,15 @@ describe('Giphy', () => { render(getAttachmentComponent({ attachment, giphyVersion: 'original' })); await waitFor(() => { const checkImageProps = ( - imageProps: Record, + imageProps: ComponentProps, specificSizedGiphyData: { height: string; url: string; width: string }, ) => { - let imageStyle = imageProps.style as - | Record - | Array>; + let imageStyle = imageProps.style as StyleProp; if (Array.isArray(imageStyle)) { imageStyle = Object.assign({}, ...imageStyle); } - expect((imageStyle as Record).height).toBe( - parseFloat(specificSizedGiphyData.height), - ); - expect((imageStyle as Record).width).toBe( - parseFloat(specificSizedGiphyData.width), - ); + expect((imageStyle as ImageStyle).height).toBe(parseFloat(specificSizedGiphyData.height)); + expect((imageStyle as ImageStyle).width).toBe(parseFloat(specificSizedGiphyData.width)); expect((imageProps.source as { uri: string }).uri).toBe(specificSizedGiphyData.url); }; checkImageProps( diff --git a/package/src/components/Channel/__tests__/Channel.test.tsx b/package/src/components/Channel/__tests__/Channel.test.tsx index 1707baf1b5..868322ef40 100644 --- a/package/src/components/Channel/__tests__/Channel.test.tsx +++ b/package/src/components/Channel/__tests__/Channel.test.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from 'react'; +import React, { type ComponentProps, useContext, useEffect } from 'react'; import { View } from 'react-native'; import { act, cleanup, render, renderHook, waitFor } from '@testing-library/react-native'; @@ -73,7 +73,10 @@ let channel: ChannelType; const user = generateUser({ id: 'id', name: 'name' }); const messages = [generateMessage({ cid: channelCid, user })]; -type RenderComponentProps = Record & { children?: React.ReactNode }; +type RenderComponentProps = Partial, 'channel'>> & { + channel?: unknown; + children?: React.ReactNode; +}; const renderComponent = ( props: RenderComponentProps = {}, @@ -282,7 +285,7 @@ describe('Channel', () => { await waitFor(() => { expect(context).toBeInstanceOf(Object); - const ctx = context as unknown as Record; + const ctx = context as unknown as typeof mockContext; expect(ctx.channel).toBeInstanceOf(Object); expect(ctx.client).toBeInstanceOf(StreamChat); expect(ctx.markRead).toBeInstanceOf(Function); @@ -325,7 +328,7 @@ describe('Channel', () => { await waitFor(() => { expect(context).toBeInstanceOf(Object); - const ctx = context as unknown as Record; + const ctx = context as unknown as typeof mockContext; expect(ctx.Attachment).toBeInstanceOf(Function); expect(ctx.editing).toBe(false); expect(ctx.messages).toBeInstanceOf(Array); @@ -461,11 +464,7 @@ describe('Channel initial load useEffect', () => { await waitFor(() => expect(Object.keys(channelState.current.state.members!)).toHaveLength(10)); }); - function getElementsAround>( - array: T[], - key: keyof T, - id: unknown, - ) { + function getElementsAround(array: T[], key: keyof T, id: unknown) { const index = array.findIndex((obj) => obj[key] === id); if (index === -1) { @@ -490,7 +489,7 @@ describe('Channel initial load useEffect', () => { const loadMessageIntoState = jest.fn(() => { const newMessages = getElementsAround( - messages as unknown as Record[], + messages as unknown as typeof channel.state.messages, 'id', messageToSearch.id, ); @@ -532,7 +531,9 @@ describe('Channel initial load useEffect', () => { jest.restoreAllMocks(); cleanup(); }); - const mockedHook = (values: Record) => + const mockedHook = ( + values: Partial>, + ) => jest.spyOn(MessageListPaginationHooks, 'useMessageListPagination').mockImplementation( () => ({ @@ -556,12 +557,12 @@ describe('Channel initial load useEffect', () => { const channel = chatClient.channel('messaging', mockedChannel.channel.id); await channel.watch(); const user = generateUser(); - const read_data: Record = {}; + const read_data: typeof channel.state.read = {}; read_data[chatClient.user!.id] = { last_read: new Date(), user, - }; + } as unknown as (typeof channel.state.read)[string]; channel.state = { ...channelInitialState, @@ -591,7 +592,7 @@ describe('Channel initial load useEffect', () => { const user = generateUser(); const numberOfUnreadMessages = 15; - const read_data: Record = {}; + const read_data: typeof channel.state.read = {}; read_data[chatClient.user!.id] = { last_read: new Date(), @@ -626,7 +627,7 @@ describe('Channel initial load useEffect', () => { const user = generateUser(); const numberOfUnreadMessages = 2; - const read_data: Record = {}; + const read_data: typeof channel.state.read = {}; read_data[chatClient.user!.id] = { last_read: new Date(), diff --git a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx index 7d222e76c6..8b4c3ed9bc 100644 --- a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx +++ b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx @@ -15,7 +15,10 @@ describe('useMessageListPagination', () => { let chatClient: StreamChat; let channel: ChannelType; - const mockedHook = (state: Record, values?: Record) => + const mockedHook = ( + state: Partial, + values?: Partial>, + ) => jest.spyOn(ChannelStateHooks, 'useChannelMessageDataState').mockImplementation( () => ({ diff --git a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx index 933e33ddc5..ddd4e276a6 100644 --- a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx +++ b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx @@ -215,7 +215,7 @@ describe('ChannelList', () => { const staleChannel = [createMockChannel('stale-channel')]; const freshChannel = [createMockChannel('new-channel')]; const spy = jest.spyOn(chatClient, 'queryChannels'); - spy.mockImplementation(((filters: Record = {}) => { + spy.mockImplementation(((filters: Parameters[0] = {}) => { if (Object.prototype.hasOwnProperty.call(filters, 'new-filter')) { return deferredCallForFreshFilter.promise; } diff --git a/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx index d3f3747c2c..f29c9754de 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { type ComponentProps } from 'react'; import { Text } from 'react-native'; import { render } from '@testing-library/react-native'; @@ -7,16 +7,19 @@ import type { Channel } from 'stream-chat'; import { ThemeProvider, defaultTheme } from '../../../contexts'; import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext'; import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelActionItems'; +import { StreamBottomSheetModalFlatList } from '../../UIComponents/StreamBottomSheetModalFlatList'; import type { ChannelDetailsHeaderProps } from '../ChannelDetailsBottomSheet'; import { ChannelDetailsBottomSheet } from '../ChannelDetailsBottomSheet'; +type StreamBottomSheetModalFlatListProps = ComponentProps; + const mockStreamBottomSheetModalFlatList = jest.fn( // eslint-disable-next-line @typescript-eslint/no-unused-vars - (_props: Record) => null, + (_props: StreamBottomSheetModalFlatListProps) => null, ); jest.mock('../../UIComponents/StreamBottomSheetModalFlatList', () => ({ - StreamBottomSheetModalFlatList: (...args: [Record]) => + StreamBottomSheetModalFlatList: (...args: [StreamBottomSheetModalFlatListProps]) => mockStreamBottomSheetModalFlatList(...args), })); @@ -77,7 +80,9 @@ describe('ChannelDetailsBottomSheet', () => { expect(mockStreamBottomSheetModalFlatList).toHaveBeenCalled(); const flatListProps = ( - mockStreamBottomSheetModalFlatList.mock.calls[0] as unknown as [Record] + mockStreamBottomSheetModalFlatList.mock.calls[0] as unknown as [ + StreamBottomSheetModalFlatListProps, + ] )?.[0]; expect(flatListProps).toEqual( expect.objectContaining({ diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx index f83f64070d..1b45f60021 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx @@ -57,7 +57,7 @@ const ChannelPreviewUIComponent = (props: ChannelPreviewUIComponentProps) => { const initChannelFromData = async ( chatClient: StreamChat, - overrides: Record = {}, + overrides: Parameters[0] = {}, ) => { const mockedChannel = generateChannelResponse(overrides); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]); @@ -98,14 +98,14 @@ describe('ChannelPreview', () => { ); }; - const generateChannelWrapper = (overrides: Record) => + const generateChannelWrapper = (overrides: Partial) => generateChannel({ countUnread: jest.fn().mockReturnValue(0), initialized: true, lastMessage: jest.fn().mockReturnValue(generateMessage()), muteStatus: jest.fn().mockReturnValue({ muted: false }), ...overrides, - }); + } as unknown as Parameters[0]); const useInitializeChannel = async (c: GetOrCreateChannelApiParams) => { useMockedApis(chatClient, [getOrCreateChannelApi(c)]); diff --git a/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx index fa3f2617e2..180a32a952 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { type ComponentProps } from 'react'; import { Text } from 'react-native'; import { act, render } from '@testing-library/react-native'; @@ -8,6 +8,7 @@ import { WithComponents } from '../../../contexts/componentsContext/ComponentsCo import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelActionItems'; import * as ChannelActionItemsModule from '../../ChannelList/hooks/useChannelActionItems'; import * as ChannelActionsModule from '../../ChannelList/hooks/useChannelActions'; +import { SwipableWrapper } from '../../UIComponents/SwipableWrapper'; import { ChannelSwipableWrapper } from '../ChannelSwipableWrapper'; import * as UseIsChannelMutedModule from '../hooks/useIsChannelMuted'; @@ -60,7 +61,7 @@ jest.mock('../../UIComponents/SwipableWrapper', () => ({ rightActionsProbe.items = items; return null; }, - SwipableWrapper: (...args: [React.PropsWithChildren>]) => + SwipableWrapper: (...args: [ComponentProps]) => mockSwipableWrapper(...args), })); diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx index c0b4c4ce5a..7d44fbe0d3 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx @@ -23,6 +23,8 @@ import { getTestClientWithUser } from '../../../../mock-builders/mock'; import { Channel } from '../../../Channel/Channel'; import { Chat } from '../../../Chat/Chat'; import { Message } from '../../Message'; +import type { MessageFooterProps } from '../MessageFooter'; +import type { MessageHeaderProps } from '../MessageHeader'; describe('MessageContent', () => { let channel: ChannelType; @@ -117,7 +119,7 @@ describe('MessageContent', () => { const user = generateUser(); const message = generateMessage({ user }); - const ContextMessageHeader = (props: Record) => ( + const ContextMessageHeader = (props: MessageHeaderProps) => ( ); @@ -143,7 +145,7 @@ describe('MessageContent', () => { const user = generateUser(); const message = generateMessage({ user }); - const ContextMessageFooter = (props: Record) => ( + const ContextMessageFooter = (props: MessageFooterProps) => ( ); diff --git a/package/src/components/UIComponents/SwipableWrapper.tsx b/package/src/components/UIComponents/SwipableWrapper.tsx index a563195680..0ce5d9b55d 100644 --- a/package/src/components/UIComponents/SwipableWrapper.tsx +++ b/package/src/components/UIComponents/SwipableWrapper.tsx @@ -32,7 +32,7 @@ const animationOptions = { export type SwipableActionItem = { action: () => void | Promise; contentContainerStyle?: StyleProp; - Content: React.ComponentType>; + Content: React.ComponentType; id: string; }; diff --git a/package/src/mock-builders/api/initiateClientWithChannels.ts b/package/src/mock-builders/api/initiateClientWithChannels.ts index 85198a9a3a..23e0df4a1c 100644 --- a/package/src/mock-builders/api/initiateClientWithChannels.ts +++ b/package/src/mock-builders/api/initiateClientWithChannels.ts @@ -8,7 +8,7 @@ import { generateMember } from '../generator/member'; import { generateUser } from '../generator/user'; import { getTestClientWithUser } from '../mock'; -type ChannelData = Record; +type ChannelData = Parameters[0]; const initChannelFromData = async ({ channelData, diff --git a/package/src/mock-builders/attachments.ts b/package/src/mock-builders/attachments.ts index b5eceab7c1..4733cdce77 100644 --- a/package/src/mock-builders/attachments.ts +++ b/package/src/mock-builders/attachments.ts @@ -20,7 +20,7 @@ export const generateLocalAttachmentData = (): LocalAttachmentData => ({ }); export const generateLocalFileUploadAttachmentData = ( - overrides?: Partial<{ file: Partial } & Record>, + overrides?: Partial }>, attachmentData?: Partial, ) => ({ localMetadata: { diff --git a/package/src/mock-builders/generator/channel.ts b/package/src/mock-builders/generator/channel.ts index 3a91114b45..dd7ed64b73 100644 --- a/package/src/mock-builders/generator/channel.ts +++ b/package/src/mock-builders/generator/channel.ts @@ -62,7 +62,6 @@ const defaultState = { }; export type GeneratedChannel = { - _client: Record; channel: Partial & { config: typeof defaultConfig }; cid: string; id: string; @@ -77,7 +76,6 @@ const getChannelDefaults = (opts: GeneratedChannelIdType = {}): GeneratedChannel const id = opts.id ?? uuidv4(); const type = opts.type ?? 'messaging'; return { - _client: {}, channel: { cid: `${type}:${id}`, config: { diff --git a/package/src/mock-builders/mock.ts b/package/src/mock-builders/mock.ts index 8af9ad3fa7..cb008b7648 100644 --- a/package/src/mock-builders/mock.ts +++ b/package/src/mock-builders/mock.ts @@ -36,10 +36,10 @@ function mockClient(client: StreamChat, options: MockClientOptions = {}): Stream const { disableAppSettings = true } = options; const c = client as MockableStreamChat; - jest.spyOn(c as unknown as Record, '_setToken' as never).mockImplementation(); - jest - .spyOn(c as unknown as Record, '_setupConnection' as never) - .mockImplementation(); + type WithPrivates = { _setToken: () => void; _setupConnection: () => void }; + const withPrivates = c as unknown as WithPrivates; + jest.spyOn(withPrivates, '_setToken').mockImplementation(); + jest.spyOn(withPrivates, '_setupConnection').mockImplementation(); c.tokenManager = { getToken: jest.fn(() => token), tokenReady: jest.fn(() => true), From b3fa7a36f1f36e8dd33c7ce8ce7f53672041f225 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Tue, 21 Apr 2026 16:55:15 +0200 Subject: [PATCH 15/15] refactor(tests): simplify over-typed casts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Focused pass to reduce redundant casts across the test suite. Cuts \`as unknown as\` occurrences from 359 → 283 (-76, ~21%) without changing runtime behavior. **Highlights:** - \`channelMocks.tsx\`: introduce local \`mockMessage\` / \`mockUser\` helpers so 17 per-literal \`as unknown as MessageResponse\` / \`as unknown as UserResponse\` casts collapse into one internal narrow cast inside each helper. - \`useMessageListPagination.test.tsx\` / \`Channel.test.tsx\` / \`MessageList.test.tsx\`: drop many \`as unknown as typeof channel.state.messages\` casts on \`generateMessage()\` outputs — the generator now returns \`LocalMessage\`, which already matches. - \`Channel.test.tsx\`: tighten \`ChannelContext as unknown as React.Context\` → \`as React.Context\` (React's \`Context\` is invariant, so a full cast isn't needed, just the single \`as\`). - Dropped \`{} as unknown as X\` → \`{} as X\` for empty provider values where the double-cast was overkill. - Dropped the \`client={chatClient}\` dead prop still lingering on one of the two \`\` renders in \`Thread.test.tsx\` (missed in the earlier dead-prop sweep). - Simplified \`as unknown as X\` to \`as X\` at ~10 miscellaneous sites (e.g. \`Parameters\`, \`jest.Mocked\`, context-prop indexers). **Intentionally kept:** - \`_fiber\` internal-access casts in offline-support helpers. - Private-member access casts (\`syncManager\`, \`_sendMessage\`, \`_setToken\`, etc.). - \`Streami18n\` \`ConstructorParameters\` double-casts where test fixtures violate the stricter option types. - \`channel.state = {...} as unknown as typeof channel.state\` where a partial spread genuinely doesn't satisfy the full ChannelState class instance shape. test:typecheck: 0 errors. Full test suite: same pre-existing SQLite-isolation flake; no regressions. --- .../offline-support/offline-feature.tsx | 2 +- .../Channel/__tests__/Channel.test.tsx | 37 ++++------- .../useMessageListPagination.test.tsx | 30 ++++----- .../__tests__/ChannelList.test.tsx | 18 ++--- .../__tests__/ChannelPreview.test.tsx | 2 +- .../components/Chat/__tests__/Chat.test.tsx | 14 ++-- .../__tests__/MessageList.test.tsx | 7 +- .../__tests__/ScrollToBottomButton.test.tsx | 8 +-- .../__tests__/MessageUserReactions.test.tsx | 2 +- .../Thread/__tests__/Thread.test.tsx | 4 +- .../src/mock-builders/api/channelMocks.tsx | 66 +++++++++++-------- .../__tests__/video-player-pool.test.ts | 18 ++--- .../src/utils/__tests__/Streami18n.test.ts | 4 +- 13 files changed, 101 insertions(+), 111 deletions(-) diff --git a/package/src/__tests__/offline-support/offline-feature.tsx b/package/src/__tests__/offline-support/offline-feature.tsx index 973544c23d..0db351df0a 100644 --- a/package/src/__tests__/offline-support/offline-feature.tsx +++ b/package/src/__tests__/offline-support/offline-feature.tsx @@ -254,7 +254,7 @@ export const Generic = () => { const filters = { foo: 'bar', type: 'messaging', - } as unknown as ChannelFilters; + } as ChannelFilters; const sort: ChannelSort = { last_updated: 1 }; const renderComponent = () => diff --git a/package/src/components/Channel/__tests__/Channel.test.tsx b/package/src/components/Channel/__tests__/Channel.test.tsx index 868322ef40..dedad14568 100644 --- a/package/src/components/Channel/__tests__/Channel.test.tsx +++ b/package/src/components/Channel/__tests__/Channel.test.tsx @@ -81,7 +81,7 @@ type RenderComponentProps = Partial, 'channe const renderComponent = ( props: RenderComponentProps = {}, callback: (ctx: unknown) => void = () => {}, - context: React.Context = ChannelContext as unknown as React.Context, + context: React.Context = ChannelContext as React.Context, ) => render( @@ -194,7 +194,7 @@ describe('Channel', () => { hasThread(thread.id); } }, - ThreadContext as unknown as React.Context, + ThreadContext as React.Context, ); rerender( @@ -213,7 +213,7 @@ describe('Channel', () => { hasThread(thread.id); } }} - context={ThreadContext as unknown as React.Context} + context={ThreadContext as React.Context} /> @@ -230,7 +230,7 @@ describe('Channel', () => { config: channel.getConfig(), id: channel.id, type: channel.type, - } as unknown as NonNullable[0]>['channel'], + }, messages: newMessages, }), ); @@ -245,7 +245,7 @@ describe('Channel', () => { () => { useMockedApis(chatClient, [queryChannelWithNewMessages(newMessages)]); }, - MessagesContext as unknown as React.Context, + MessagesContext as React.Context, ); await waitFor(() => expect(channelQuerySpy).toHaveBeenCalled()); @@ -254,7 +254,7 @@ describe('Channel', () => { describe('ChannelContext', () => { it('renders children without crashing', async () => { const { getByTestId } = render( - + , ); @@ -275,7 +275,7 @@ describe('Channel', () => { render( } + context={ChannelContext as React.Context} fn={(ctx) => { context = ctx as ChannelContextValue; }} @@ -297,7 +297,7 @@ describe('Channel', () => { describe('MessagesContext', () => { it('renders children without crashing', async () => { const { getByTestId } = render( - + , ); @@ -318,7 +318,7 @@ describe('Channel', () => { render( } + context={MessagesContext as React.Context} fn={(ctx) => { context = ctx as MessagesContextValue; }} @@ -340,7 +340,7 @@ describe('Channel', () => { describe('ThreadContext', () => { it('renders children without crashing', async () => { const { getByTestId } = render( - + , ); @@ -361,7 +361,7 @@ describe('Channel', () => { render( } + context={ThreadContext as React.Context} fn={(ctx) => { context = ctx as ThreadContextValue; }} @@ -436,12 +436,7 @@ describe('Channel initial load useEffect', () => { channel.state = { ...channelInitialState, members: Object.fromEntries( - Array.from({ length: 10 }, (_, i) => [ - i, - generateMember({ user_id: String(i) } as unknown as Partial< - Parameters[0] - >), - ]), + Array.from({ length: 10 }, (_, i) => [i, generateMember({ user_id: String(i) })]), ), messagePagination: { hasPrev: true, @@ -488,12 +483,8 @@ describe('Channel initial load useEffect', () => { await channel.watch(); const loadMessageIntoState = jest.fn(() => { - const newMessages = getElementsAround( - messages as unknown as typeof channel.state.messages, - 'id', - messageToSearch.id, - ); - channel.state.messages = newMessages as unknown as typeof channel.state.messages; + const newMessages = getElementsAround(messages, 'id', messageToSearch.id); + channel.state.messages = newMessages; }); channel.state = { diff --git a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx index 8b4c3ed9bc..4f6eeea3bf 100644 --- a/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx +++ b/package/src/components/Channel/__tests__/useMessageListPagination.test.tsx @@ -63,7 +63,7 @@ describe('useMessageListPagination', () => { const loadMessageIntoState = jest.fn(() => { channel.state.messages = Array.from({ length: 20 }, (_, i) => generateMessage({ text: `message-${i}` }), - ) as unknown as typeof channel.state.messages; + ); (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { @@ -104,7 +104,7 @@ describe('useMessageListPagination', () => { hasPrev: false, }, } as unknown as typeof channel.state; - channel.query = queryFn as unknown as typeof channel.query; + channel.query = queryFn as typeof channel.query; const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { @@ -125,7 +125,7 @@ describe('useMessageListPagination', () => { hasPrev: true, }, } as unknown as typeof channel.state; - channel.query = queryFn as unknown as typeof channel.query; + channel.query = queryFn as typeof channel.query; mockedHook({ loadingMore: true, loadingMoreRecent: true }); @@ -147,7 +147,7 @@ describe('useMessageListPagination', () => { const queryFn = jest.fn(() => { channel.state.messages = Array.from({ length: 40 }, (_, i) => generateMessage({ text: `message-${i}` }), - ) as unknown as typeof channel.state.messages; + ); (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { @@ -197,7 +197,7 @@ describe('useMessageListPagination', () => { hasPrev: true, }, } as unknown as typeof channel.state; - channel.query = queryFn as unknown as typeof channel.query; + channel.query = queryFn as typeof channel.query; const { result } = renderHook(() => useMessageListPagination({ channel })); await act(async () => { @@ -218,7 +218,7 @@ describe('useMessageListPagination', () => { hasPrev: true, }, } as unknown as typeof channel.state; - channel.query = queryFn as unknown as typeof channel.query; + channel.query = queryFn as typeof channel.query; mockedHook({ loadingMore: true, loadingMoreRecent: true }); @@ -240,7 +240,7 @@ describe('useMessageListPagination', () => { const queryFn = jest.fn(() => { channel.state.messages = Array.from({ length: 40 }, (_, i) => generateMessage({ text: `message-${i}` }), - ) as unknown as typeof channel.state.messages; + ); (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { @@ -283,7 +283,7 @@ describe('useMessageListPagination', () => { const loadMessageIntoState = jest.fn(() => { channel.state.messages = Array.from({ length: 20 }, (_, i) => generateMessage({ text: `message-${i}` }), - ) as unknown as typeof channel.state.messages; + ); (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { @@ -309,7 +309,7 @@ describe('useMessageListPagination', () => { const loadMessageIntoState = jest.fn(() => { channel.state.messages = Array.from({ length: 20 }, (_, i) => generateMessage({ text: `message-${i}` }), - ) as unknown as typeof channel.state.messages; + ); (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { @@ -350,7 +350,7 @@ describe('useMessageListPagination', () => { generateMessage({ text: `message-${i}` }), ); const loadMessageIntoState = jest.fn(() => { - channel.state.messages = messages as unknown as typeof channel.state.messages; + channel.state.messages = messages; (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); channel.state = { @@ -446,7 +446,7 @@ describe('useMessageListPagination', () => { const newMessages = Array.from({ length: 20 }, (_, i) => generateMessage({ id: String(i + 21), text: `message-${i + 21}` }), ); - channel.state.messages = newMessages as unknown as typeof channel.state.messages; + channel.state.messages = newMessages; (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); (channel.state as unknown as { loadMessageIntoState: jest.Mock }).loadMessageIntoState = @@ -489,7 +489,7 @@ describe('useMessageListPagination', () => { const newMessages = Array.from({ length: 20 }, (_, i) => generateMessage({ id: String(i + 21), text: `message-${i + 21}` }), ); - channel.state.messages = newMessages as unknown as typeof channel.state.messages; + channel.state.messages = newMessages; (channel.state.messagePagination as { hasPrev: boolean }).hasPrev = true; }); (channel.state as unknown as { loadMessageIntoState: jest.Mock }).loadMessageIntoState = @@ -581,9 +581,9 @@ describe('useMessageListPagination', () => { const user = generateUser(); it.each` - scenario | last_read | expectedQueryCalls | expectedJumpToMessageFinishedCalls | expectedSetChannelUnreadStateCalls | expectedSetTargetedMessageCalls | expectedTargetedMessageId - ${'when last_read matches a message'} | ${new Date(messages[10].created_at as unknown as Date)} | ${0} | ${1} | ${1} | ${1} | ${'10'} - ${'when last_read does not match any message'} | ${new Date('2021-09-02T00:00:00.000Z')} | ${1} | ${0} | ${0} | ${0} | ${undefined} + scenario | last_read | expectedQueryCalls | expectedJumpToMessageFinishedCalls | expectedSetChannelUnreadStateCalls | expectedSetTargetedMessageCalls | expectedTargetedMessageId + ${'when last_read matches a message'} | ${new Date(messages[10].created_at)} | ${0} | ${1} | ${1} | ${1} | ${'10'} + ${'when last_read does not match any message'} | ${new Date('2021-09-02T00:00:00.000Z')} | ${1} | ${0} | ${0} | ${0} | ${undefined} `( '$scenario', async ({ diff --git a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx index ddd4e276a6..4ebc8e91de 100644 --- a/package/src/components/ChannelList/__tests__/ChannelList.test.tsx +++ b/package/src/components/ChannelList/__tests__/ChannelList.test.tsx @@ -43,8 +43,8 @@ const mockChannelSwipableWrapper = jest.fn(({ children }: { children: React.Reac )); jest.mock('../../ChannelPreview/ChannelSwipableWrapper', () => ({ - ChannelSwipableWrapper: (...args: unknown[]) => - (mockChannelSwipableWrapper as unknown as (...a: unknown[]) => React.ReactElement)(...args), + ChannelSwipableWrapper: (...args: Parameters) => + mockChannelSwipableWrapper(...args), })); /** @@ -173,11 +173,7 @@ describe('ChannelList', () => { ['filters'] - } + filters={{ dummyFilter: true } as React.ComponentProps['filters']} /> , @@ -220,14 +216,14 @@ describe('ChannelList', () => { return deferredCallForFreshFilter.promise; } return deferredCallForStaleFilter.promise; - }) as unknown as typeof chatClient.queryChannels); + }) as typeof chatClient.queryChannels); const { rerender, queryByTestId } = render( ['filters']} + filters={staleFilter as React.ComponentProps['filters']} /> , @@ -250,7 +246,7 @@ describe('ChannelList', () => { ['filters']} + filters={freshFilter as React.ComponentProps['filters']} /> , @@ -922,7 +918,7 @@ describe('ChannelList', () => { chatClient.queryChannels = jest.fn( () => deferredPromise.promise, - ) as unknown as typeof chatClient.queryChannels; + ) as typeof chatClient.queryChannels; act(() => dispatchConnectionChangedEvent(chatClient, false)); act(() => dispatchConnectionChangedEvent(chatClient, true)); diff --git a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx index 1b45f60021..011e1237d0 100644 --- a/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx +++ b/package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx @@ -68,7 +68,7 @@ const initChannelFromData = async ( channel.initialized = true; channel.lastMessage = jest.fn().mockReturnValue(generateMessage()); channel.muteStatus = jest.fn().mockReturnValue({ muted: false }); - channel.state.messages = [generateMessage()] as unknown as typeof channel.state.messages; + channel.state.messages = [generateMessage()]; return channel; }; diff --git a/package/src/components/Chat/__tests__/Chat.test.tsx b/package/src/components/Chat/__tests__/Chat.test.tsx index 9f382c0564..945e04b376 100644 --- a/package/src/components/Chat/__tests__/Chat.test.tsx +++ b/package/src/components/Chat/__tests__/Chat.test.tsx @@ -168,9 +168,8 @@ describe('TranslationContext', () => { const i18nInstance = new Streami18n(); const { t, tDateTimeParser } = await i18nInstance.getTranslators(); - i18nInstance.t = (() => 't') as unknown as typeof i18nInstance.t; - i18nInstance.tDateTimeParser = (() => - 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; + i18nInstance.t = (() => 't') as typeof i18nInstance.t; + i18nInstance.tDateTimeParser = (() => 'tDateTimeParser') as typeof i18nInstance.tDateTimeParser; render( @@ -194,9 +193,8 @@ describe('TranslationContext', () => { let context: TranslationContextValue = {} as TranslationContextValue; const i18nInstance = new Streami18n(); - i18nInstance.t = (() => 't') as unknown as typeof i18nInstance.t; - i18nInstance.tDateTimeParser = (() => - 'tDateTimeParser') as unknown as typeof i18nInstance.tDateTimeParser; + i18nInstance.t = (() => 't') as typeof i18nInstance.t; + i18nInstance.tDateTimeParser = (() => 'tDateTimeParser') as typeof i18nInstance.tDateTimeParser; const { rerender } = render( @@ -215,9 +213,9 @@ describe('TranslationContext', () => { const newI18nInstance = new Streami18n(); - newI18nInstance.t = (() => 'newT') as unknown as typeof newI18nInstance.t; + newI18nInstance.t = (() => 'newT') as typeof newI18nInstance.t; newI18nInstance.tDateTimeParser = (() => - 'newtDateTimeParser') as unknown as typeof newI18nInstance.tDateTimeParser; + 'newtDateTimeParser') as typeof newI18nInstance.tDateTimeParser; rerender( diff --git a/package/src/components/MessageList/__tests__/MessageList.test.tsx b/package/src/components/MessageList/__tests__/MessageList.test.tsx index de0cf806fe..e5880f739d 100644 --- a/package/src/components/MessageList/__tests__/MessageList.test.tsx +++ b/package/src/components/MessageList/__tests__/MessageList.test.tsx @@ -590,12 +590,7 @@ describe('MessageList pagination', () => { ...channelInitialState, latestMessages: [], members: Object.fromEntries( - Array.from({ length: 10 }, (_, i) => [ - i, - generateMember({ user_id: String(i) } as unknown as Partial< - Parameters[0] - >), - ]), + Array.from({ length: 10 }, (_, i) => [i, generateMember({ user_id: String(i) })]), ), messages: Array.from({ length: 10 }, (_, i) => generateMessage({ id: String(i) })), messageSets: [{ isCurrent: true, isLatest: true }], diff --git a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx index 6fb05aadde..a057b76254 100644 --- a/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx +++ b/package/src/components/MessageList/__tests__/ScrollToBottomButton.test.tsx @@ -21,7 +21,7 @@ describe('ScrollToBottomButton', () => { const translators = await i18nInstance.getTranslators(); const { queryByTestId } = render( - + null} showNotification={false} /> , @@ -37,7 +37,7 @@ describe('ScrollToBottomButton', () => { const translators = await i18nInstance.getTranslators(); const { queryByTestId } = render( - + null} showNotification={true} /> , @@ -54,7 +54,7 @@ describe('ScrollToBottomButton', () => { const onPress = jest.fn(); const { getByTestId } = render( - + , @@ -85,7 +85,7 @@ describe('ScrollToBottomButton', () => { const translators = await i18nInstance.getTranslators(); const { toJSON } = render( - + null} showNotification={true} /> , diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx index 2f19f68612..aaf535d2e1 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx @@ -51,7 +51,7 @@ const renderComponent = (props = {}) => ), }} > - + diff --git a/package/src/components/Thread/__tests__/Thread.test.tsx b/package/src/components/Thread/__tests__/Thread.test.tsx index 21de58153c..72577b01d1 100644 --- a/package/src/components/Thread/__tests__/Thread.test.tsx +++ b/package/src/components/Thread/__tests__/Thread.test.tsx @@ -38,7 +38,7 @@ const renderComponent = ({ return render( - + @@ -156,7 +156,7 @@ describe('Thread', () => { } > ['value']} + value={{} as React.ComponentProps['value']} > diff --git a/package/src/mock-builders/api/channelMocks.tsx b/package/src/mock-builders/api/channelMocks.tsx index c2b8c59891..74f9fb069f 100644 --- a/package/src/mock-builders/api/channelMocks.tsx +++ b/package/src/mock-builders/api/channelMocks.tsx @@ -1,3 +1,4 @@ +import { fromPartial } from '@total-typescript/shoehorn'; import type { Attachment, Channel, LocalMessage, MessageResponse, UserResponse } from 'stream-chat'; import { @@ -6,16 +7,25 @@ import { ONE_MEMBER_WITH_EMPTY_USER, } from '../../mock-builders/api/queryMembers'; +// Test fixtures intentionally supply runtime-shaped values (Date objects for +// date fields, custom `type` strings, a mock `Channel` instance for the +// `channel` prop) that do not match the strict server-side `MessageResponse` +// schema. Accept an unknown-value record and hide the single cast inside the +// helper so call sites stay flat. +const mockMessage = (data: Record) => + fromPartial(data as Partial); +const mockUser = (data: Partial) => fromPartial(data); + const channelName = 'okechukwu'; -const CHANNEL = { +const CHANNEL = fromPartial({ data: { name: channelName }, state: { messages: [] }, -} as unknown as Channel; +}); const CHANNEL_WITH_MESSAGES_TEXT = { members: CHANNEL_MEMBERS, messages: [ - { + mockMessage({ args: 'string', attachments: [], channel: CHANNEL, @@ -27,9 +37,9 @@ const CHANNEL_WITH_MESSAGES_TEXT = { id: 'ljkblk', text: 'jkbkbiubicbi', type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, - { + user: mockUser({ id: 'okechukwu' }), + }), + mockMessage({ args: 'string', attachments: [], channel: CHANNEL, @@ -41,8 +51,8 @@ const CHANNEL_WITH_MESSAGES_TEXT = { id: 'jbkjb', text: 'jkbkbiubicbi', type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, + user: mockUser({ id: 'okechukwu' }), + }), ], name: channelName, }; @@ -58,7 +68,7 @@ const CHANNEL_WITH_NO_MESSAGES = { const CHANNEL_WITH_MESSAGE_COMMAND = { members: CHANNEL_MEMBERS, messages: [ - { + mockMessage({ args: 'string', attachments: [], channel: CHANNEL, @@ -68,9 +78,9 @@ const CHANNEL_WITH_MESSAGE_COMMAND = { 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, - { + user: mockUser({ id: 'okechukwu' }), + }), + mockMessage({ args: 'string', attachments: [], channel: CHANNEL, @@ -80,15 +90,15 @@ const CHANNEL_WITH_MESSAGE_COMMAND = { 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, + user: mockUser({ id: 'okechukwu' }), + }), ], }; const CHANNEL_WITH_MESSAGES_ATTACHMENTS = { members: CHANNEL_MEMBERS, messages: [ - { + mockMessage({ args: 'string', attachments: [ { @@ -120,13 +130,13 @@ const CHANNEL_WITH_MESSAGES_ATTACHMENTS = { 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, + user: mockUser({ id: 'okechukwu' }), + }), ], name: channelName, }; -const LATEST_MESSAGE = { +const LATEST_MESSAGE = mockMessage({ args: 'string', attachments: [], channel: CHANNEL, @@ -138,8 +148,8 @@ const LATEST_MESSAGE = { id: 'string', text: 'jkbkbiubicbi', type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, -} as unknown as MessageResponse; + user: mockUser({ id: 'okechukwu' }), +}); const FORMATTED_MESSAGE: LocalMessage = { created_at: new Date('2021-02-12T12:12:35.862282Z'), @@ -154,7 +164,7 @@ const FORMATTED_MESSAGE: LocalMessage = { const CHANNEL_WITH_MENTIONED_USERS = { members: ONE_MEMBER_WITH_EMPTY_USER, messages: [ - { + mockMessage({ args: 'string', attachments: [], cid: 'stridkncnng', @@ -167,8 +177,8 @@ const CHANNEL_WITH_MENTIONED_USERS = { { id: 'Enzo', name: 'Enzo' }, ] as UserResponse[], text: 'Max', - } as unknown as MessageResponse, - { + }), + mockMessage({ args: 'string', attachments: [], cid: 'stridodong', @@ -181,14 +191,14 @@ const CHANNEL_WITH_MENTIONED_USERS = { { id: 'Enzo', name: 'Enzo' }, ] as UserResponse[], text: 'Max', - } as unknown as MessageResponse, + }), ], }; const CHANNEL_WITH_EMPTY_MESSAGE = { members: ONE_MEMBER_WITH_EMPTY_USER, messages: [ - { + mockMessage({ args: 'string', attachments: [], cid: 'stridkncnng', @@ -200,8 +210,8 @@ const CHANNEL_WITH_EMPTY_MESSAGE = { { id: 'Ada', name: 'Ada' }, { id: 'Enzo', name: 'Enzo' }, ] as UserResponse[], - } as unknown as MessageResponse, - { + }), + mockMessage({ args: 'string', attachments: [], cid: 'stridodong', @@ -213,7 +223,7 @@ const CHANNEL_WITH_EMPTY_MESSAGE = { { id: 'Ada', name: 'Ada' }, { id: 'Enzo', name: 'Enzo' }, ] as UserResponse[], - } as unknown as MessageResponse, + }), ], }; diff --git a/package/src/state-store/__tests__/video-player-pool.test.ts b/package/src/state-store/__tests__/video-player-pool.test.ts index 4d5defe4c9..4d64855a3f 100644 --- a/package/src/state-store/__tests__/video-player-pool.test.ts +++ b/package/src/state-store/__tests__/video-player-pool.test.ts @@ -20,7 +20,7 @@ const createMockPlayer = (id: string, overrides: Partial = {}): Vid pause: jest.fn(), play: jest.fn(), ...overrides, - }) as unknown as VideoPlayer; + }) as VideoPlayer; describe('VideoPlayerPool', () => { beforeEach(() => { @@ -175,7 +175,7 @@ describe('VideoPlayerPool', () => { const pool = new VideoPlayerPool(); const player = pool.getOrAddPlayer({ id: 'active-player' }); - pool.setActivePlayer(player as unknown as VideoPlayer); + pool.setActivePlayer(player); expect(pool.getActivePlayer()).toBe(player); pool.removePlayer('active-player'); @@ -188,7 +188,7 @@ describe('VideoPlayerPool', () => { const activePlayer = pool.getOrAddPlayer({ id: 'active-player' }); pool.getOrAddPlayer({ id: 'other-player' }); - pool.setActivePlayer(activePlayer as unknown as VideoPlayer); + pool.setActivePlayer(activePlayer); pool.removePlayer('other-player'); expect(pool.getActivePlayer()).toBe(activePlayer); @@ -255,7 +255,7 @@ describe('VideoPlayerPool', () => { const pool = new VideoPlayerPool(); const player = pool.getOrAddPlayer({ id: 'active-player' }); - pool.setActivePlayer(player as unknown as VideoPlayer); + pool.setActivePlayer(player); expect(pool.getActivePlayer()).toBe(player); pool.clear(); @@ -277,7 +277,7 @@ describe('VideoPlayerPool', () => { it('should not change active player when player does not exist', () => { const pool = new VideoPlayerPool(); const existingPlayer = pool.getOrAddPlayer({ id: 'existing-player' }); - pool.setActivePlayer(existingPlayer as unknown as VideoPlayer); + pool.setActivePlayer(existingPlayer); pool.requestPlay('non-existent-player'); @@ -336,7 +336,7 @@ describe('VideoPlayerPool', () => { const pool = new VideoPlayerPool(); const player = pool.getOrAddPlayer({ id: 'active-player' }); - pool.setActivePlayer(player as unknown as VideoPlayer); + pool.setActivePlayer(player); expect(pool.getActivePlayer()).toBe(player); pool.notifyPaused(); @@ -369,7 +369,7 @@ describe('VideoPlayerPool', () => { ({ activeVideoPlayer }) => callback(activeVideoPlayer), ); - pool.setActivePlayer(player as unknown as VideoPlayer); + pool.setActivePlayer(player); expect(callback).toHaveBeenCalledWith(player); }); @@ -377,7 +377,7 @@ describe('VideoPlayerPool', () => { it('should notify subscribers when active player is cleared', () => { const pool = new VideoPlayerPool(); const player = pool.getOrAddPlayer({ id: 'player-1' }); - pool.setActivePlayer(player as unknown as VideoPlayer); + pool.setActivePlayer(player); const callback = jest.fn(); pool.state.subscribeWithSelector( @@ -420,7 +420,7 @@ describe('VideoPlayerPool', () => { const player1 = pool.getOrAddPlayer({ id: 'player-1' }); pool.getOrAddPlayer({ id: 'player-2' }); - pool.setActivePlayer(player1 as unknown as VideoPlayer); + pool.setActivePlayer(player1); pool.removePlayer('player-1'); expect(pool.getActivePlayer()).toBeNull(); diff --git a/package/src/utils/__tests__/Streami18n.test.ts b/package/src/utils/__tests__/Streami18n.test.ts index 8287508e39..d8d631fbec 100644 --- a/package/src/utils/__tests__/Streami18n.test.ts +++ b/package/src/utils/__tests__/Streami18n.test.ts @@ -273,7 +273,7 @@ describe('Streami18n timezone', () => { it('allows to override the default timestampFormatter', async () => { const i18n = new Streami18n({ formatters: { timestampFormatter: () => () => 'custom' }, - translationsForLanguage: { abc: '{{ value | timestampFormatter }}' } as unknown as Record< + translationsForLanguage: { abc: '{{ value | timestampFormatter }}' } as Record< string, string >, @@ -284,7 +284,7 @@ describe('Streami18n timezone', () => { it('allows to add new custom formatter', async () => { const i18n = new Streami18n({ formatters: { customFormatter: () => () => 'custom' }, - translationsForLanguage: { abc: '{{ value | customFormatter }}' } as unknown as Record< + translationsForLanguage: { abc: '{{ value | customFormatter }}' } as Record< string, string >,