Skip to content

refactor(tests): migrate test suite and mock-builders to TypeScript#3562

Open
oliverlaz wants to merge 15 commits intodevelopfrom
chore/migrate-tests-to-typescript
Open

refactor(tests): migrate test suite and mock-builders to TypeScript#3562
oliverlaz wants to merge 15 commits intodevelopfrom
chore/migrate-tests-to-typescript

Conversation

@oliverlaz
Copy link
Copy Markdown
Member

@oliverlaz oliverlaz commented Apr 20, 2026

Summary

Migrates the remaining JS test infrastructure to TypeScript, then type-tightens the whole test suite with full noImplicitAny: true strictness and wires yarn test:typecheck into CI as a required check.

Migration (baseline)

  • 44 mock-builders (package/src/mock-builders/): API response builders, event dispatchers, data generators, and the core mock.ts client helper
  • 36 .test.js files β†’ .test.ts / .test.tsx across components and utils
  • 2 offline-support helpers β†’ .tsx
  • test-utils/BetterSqlite.js β†’ .ts
  • jest-setup.js β†’ .tsx (contains JSX for the BottomSheetModal mock)
  • New package/tsconfig.test.json so tests and mock-builders are type-checked (base tsconfig.json still excludes them from the published build)
  • New yarn test:typecheck script, wired into .github/workflows/check-pr.yml

Type-tightening

  • fix(store,messagelist): type-annotate empty arrays in 8 source files where const queries = [] was inferring never[] under the broader include scope. Real latent issues; no behavior change.
  • refactor(mock-builders): tighten types in generators and event dispatchers using SDK types from stream-chat; drop fields not on Attachment; add missing reminders on default channel config; narrow client.channel(...) args.
  • refactor(mock-builders): generateMessage now returns LocalMessage. Previously typed as MessageResponse but at runtime produced Date objects for created_at/updated_at/pinned_at β€” the LocalMessage shape. Making the type honest eliminated 33 as unknown as LocalMessage casts and 57 toLocalMessage(…) wrapper calls across the suite. API mocks and event dispatchers that legitimately need the wire shape accept MessageResponse | LocalMessage at their boundary.
  • refactor(tests): annotate all test bodies. Uses the established {...} as unknown as XContextValue pattern for partial context mocks. Replaces every bogus as unknown as FileUpload (leftover silent bug β€” FileUpload was never imported) with the correct SDK type (LocalAudioAttachment / LocalVoiceRecordingAttachment). Uses LocalAttachment (not Partial<Attachment>) for attachment mocks that set localMetadata. Uses ComponentProps<typeof X> for renderComponent({ props }) typings.
  • refactor(tests): flip noImplicitAny: true and annotate the ~630 resulting errors across ~20 test files. let chatClient: StreamChat, typed destructured params, typed jest.fn() callbacks, ComponentProps<typeof X> for helper prop shapes. Zero any / as any added.

Dead-prop cleanup

Removing spread+cast and @ts-ignore escape hatches made TypeScript surface a slew of props tests were passing that target components don't actually accept. 15+ dead props removed across 16 test files. Notable:

  • MessageAuthor: alignment, groupStyles (live on MessageContextValue; MessageAuthor doesn't pick them).
  • MessageReplies: groupStyles, MessageRepliesAvatars, openThread (typo β€” real prop is onOpenThread; silently dropped).
  • Message: reactionsEnabled, MessageFooter override.
  • ScrollToBottomButton: t (supplied via TranslationProvider).
  • ChannelPreviewView: client, latestMessagePreview, watchers, latestMessage, latestMessageLength.
  • MessageList: channelUnreadState (internal state, never a prop).
  • Channel: client (comes from ChatContext, caught on Thread.test.tsx).
  • Giphy test helper: widened Record<string, unknown> β†’ ComponentProps<typeof Giphy>.

Also: zero @ts-ignore / @ts-expect-error directives remain in any *.test.* file.

Dependency changes

  • @types/jest: ^29.5.14 β†’ ^30.0.0 (matches installed jest@30)
  • jest: ^30.0.0 β†’ ^30.3.0
  • @total-typescript/shoehorn: new devDependency β€” fromPartial<T>() for type-safe partial mocks, replacing as any / Record<string, any> patterns

Source-file changes

All zero-behavior-change type annotations:

  • src/store/apis/{addPendingTask,deleteMessage,upsertDraft}.ts
  • src/store/sqlite-utils/{appendOrderByClause,appendWhereCluase,createCreateTableQuery}.ts
  • src/components/MessageList/hooks/useMessageList.ts
  • src/components/Message/MessageItemView/utils/renderText.tsx β€” @ts-expect-error on the untyped react-native-markdown-package import switched to @ts-ignore so both base and test tsconfigs agree.

Follow-ups (out of scope)

  • package/src/store/apis/upsertDraft.ts:55 β€” queries.concat(query) is a no-op (returns a new array that's never used). Kept out of scope since fixing it is a behavior change on production code.
  • MessageStatus.test.tsx had it.each('string', fn) (malformed β€” string iterated as characters). Converted to it.skip to preserve pre-migration runtime behavior. Un-skipping and rewriting is a follow-up.

Test plan

  • yarn build passes
  • yarn lint passes
  • yarn test:typecheck β€” 0 errors with noImplicitAny: true and full strict mode
  • yarn test:unit β€” 751 passed, 14 skipped. Only the pre-existing SQLite-isolation flake in offline-support/index.test.ts fails intermittently (baseline on develop was 5 failures; this branch is 1 β€” no regressions)
  • Zero @ts-ignore / @ts-expect-error directives remain in any *.test.* file
  • yarn test:typecheck is wired as a required check in .github/workflows/check-pr.yml
  • CI green on push

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<T>() for
  type-safe partial mocks, replacing `as any` / `Record<string, any>`)

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).
@Stream-SDK-Bot
Copy link
Copy Markdown
Contributor

Stream-SDK-Bot commented Apr 20, 2026

SDK Size

title develop branch diff status
js_bundle_size 353 KB 353 KB +178 B 🟒

…erence

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.
…chers

- 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.
…est.json

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<void> }` 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).
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<typeof X>` 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.
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<typeof X>`.
- `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<Attachment>`.

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.
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.
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.
- 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.
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<typeof X>)}`
    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<string, unknown>` to
  `props: ComponentProps<typeof Giphy>` 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.
Second pass of the dead-props sweep now that the low-hanging
spread+cast pattern is gone.

- MessageList.test.tsx: remove \`channelUnreadState\` prop from
  \`<MessageList {...messageListProps} />\`. 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>\`.
  \`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<typeof Chat>['style']\` on the
  \`style\` prop. Use the direct
  \`defaultTheme as DeepPartial<Theme>\` cast (same pattern used by
  other tests in the repo).

test:typecheck: 0 errors. Full test suite: same pre-existing
SQLite-isolation flake.
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 \`<Text>{node}</Text>\` with \`<Text>{JSON.stringify(node)}</Text>\`
    so the \`ASTNode\` value is a valid React child; the test only
    asserts \`getByText\`, the content shape is incidental.
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<typeof X>\`, \`Partial<ChannelProps>\`,
  \`ComponentOverrides\`, or \`PropsWithChildren<Partial<...>>\`.
- 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<ReturnType<typeof
  channel.messageComposer.compose>>\` 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.
Cuts `Record<string, unknown>` 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<typeof generateChannel>[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<LocalAttachmentData & { file: Partial<FileReference> }>`
  (both callers pass no overrides anyway).
- mock-builders/mock.ts: replace two `as unknown as Record<string,
  unknown>` + `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 `<Content />` with no props).

**Tests:**
- Channel.test.tsx: 9 uses replaced β€” `RenderComponentProps` becomes
  `Partial<ComponentProps<typeof Channel>>` (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<ReturnType<typeof
  MessageListPaginationHooks.useMessageListPagination>>`; `typeof
  channel.state.read`.
- Giphy.test.tsx: 10 uses replaced with `ComponentProps<typeof Image>`
  + `StyleProp<ImageStyle>` / `ImageStyle` from `react-native`.
- ChannelList.test.tsx: `filters` arg typed as
  `Parameters<typeof chatClient.queryChannels>[0]`.
- MessageContent.test.tsx: override-component prop types switched to
  `MessageHeaderProps` / `MessageFooterProps` from the component
  sources.
- useMessageListPagination.test.tsx: `mockedHook` args typed as
  `Partial<typeof channelInitialState>` and `Partial<ReturnType<
  typeof ChannelStateHooks.useChannelMessageDataState>>`.
- ChannelDetailsBottomSheet.test.tsx: 3 uses replaced with
  `ComponentProps<typeof StreamBottomSheetModalFlatList>`.
- ChannelSwipableWrapper.test.tsx: `ComponentProps<typeof
  SwipableWrapper>`.
- ChannelPreview.test.tsx: override-map types from
  `Parameters<typeof generateChannelResponse>[0]` and `Partial<Channel>`.

test:typecheck: 0 errors. Full test suite: same pre-existing
SQLite-isolation flake; no regressions.
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<unknown>\` β†’ \`as React.Context<unknown>\` (React's
  \`Context<T>\` 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 \`<Channel>\` 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<T>\`, \`jest.Mocked<T>\`, 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants