Skip to content

Commit 7642d24

Browse files
committed
refactor: replace Record<string, unknown> with specific types
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.
1 parent e43ed04 commit 7642d24

13 files changed

Lines changed: 56 additions & 57 deletions

File tree

package/src/components/Attachment/__tests__/Giphy.test.tsx

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { ComponentProps } from 'react';
2+
import type { Image, ImageStyle, StyleProp } from 'react-native';
23

34
import {
45
act,
@@ -196,21 +197,15 @@ describe('Giphy', () => {
196197
render(getAttachmentComponent({ attachment, giphyVersion: 'fixed_height' }));
197198
await waitFor(() => {
198199
const checkImageProps = (
199-
imageProps: Record<string, unknown>,
200+
imageProps: ComponentProps<typeof Image>,
200201
specificSizedGiphyData: { height: string; url: string; width: string },
201202
) => {
202-
let imageStyle = imageProps.style as
203-
| Record<string, unknown>
204-
| Array<Record<string, unknown>>;
203+
let imageStyle = imageProps.style as StyleProp<ImageStyle>;
205204
if (Array.isArray(imageStyle)) {
206205
imageStyle = Object.assign({}, ...imageStyle);
207206
}
208-
expect((imageStyle as Record<string, unknown>).height).toBe(
209-
parseFloat(specificSizedGiphyData.height),
210-
);
211-
expect((imageStyle as Record<string, unknown>).width).toBe(
212-
parseFloat(specificSizedGiphyData.width),
213-
);
207+
expect((imageStyle as ImageStyle).height).toBe(parseFloat(specificSizedGiphyData.height));
208+
expect((imageStyle as ImageStyle).width).toBe(parseFloat(specificSizedGiphyData.width));
214209
expect((imageProps.source as { uri: string }).uri).toBe(specificSizedGiphyData.url);
215210
};
216211
checkImageProps(
@@ -221,21 +216,15 @@ describe('Giphy', () => {
221216
render(getAttachmentComponent({ attachment, giphyVersion: 'original' }));
222217
await waitFor(() => {
223218
const checkImageProps = (
224-
imageProps: Record<string, unknown>,
219+
imageProps: ComponentProps<typeof Image>,
225220
specificSizedGiphyData: { height: string; url: string; width: string },
226221
) => {
227-
let imageStyle = imageProps.style as
228-
| Record<string, unknown>
229-
| Array<Record<string, unknown>>;
222+
let imageStyle = imageProps.style as StyleProp<ImageStyle>;
230223
if (Array.isArray(imageStyle)) {
231224
imageStyle = Object.assign({}, ...imageStyle);
232225
}
233-
expect((imageStyle as Record<string, unknown>).height).toBe(
234-
parseFloat(specificSizedGiphyData.height),
235-
);
236-
expect((imageStyle as Record<string, unknown>).width).toBe(
237-
parseFloat(specificSizedGiphyData.width),
238-
);
226+
expect((imageStyle as ImageStyle).height).toBe(parseFloat(specificSizedGiphyData.height));
227+
expect((imageStyle as ImageStyle).width).toBe(parseFloat(specificSizedGiphyData.width));
239228
expect((imageProps.source as { uri: string }).uri).toBe(specificSizedGiphyData.url);
240229
};
241230
checkImageProps(

package/src/components/Channel/__tests__/Channel.test.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect } from 'react';
1+
import React, { type ComponentProps, useContext, useEffect } from 'react';
22
import { View } from 'react-native';
33

44
import { act, cleanup, render, renderHook, waitFor } from '@testing-library/react-native';
@@ -73,7 +73,10 @@ let channel: ChannelType;
7373
const user = generateUser({ id: 'id', name: 'name' });
7474
const messages = [generateMessage({ cid: channelCid, user })];
7575

76-
type RenderComponentProps = Record<string, unknown> & { children?: React.ReactNode };
76+
type RenderComponentProps = Partial<Omit<ComponentProps<typeof Channel>, 'channel'>> & {
77+
channel?: unknown;
78+
children?: React.ReactNode;
79+
};
7780

7881
const renderComponent = (
7982
props: RenderComponentProps = {},
@@ -282,7 +285,7 @@ describe('Channel', () => {
282285

283286
await waitFor(() => {
284287
expect(context).toBeInstanceOf(Object);
285-
const ctx = context as unknown as Record<string, unknown>;
288+
const ctx = context as unknown as typeof mockContext;
286289
expect(ctx.channel).toBeInstanceOf(Object);
287290
expect(ctx.client).toBeInstanceOf(StreamChat);
288291
expect(ctx.markRead).toBeInstanceOf(Function);
@@ -325,7 +328,7 @@ describe('Channel', () => {
325328

326329
await waitFor(() => {
327330
expect(context).toBeInstanceOf(Object);
328-
const ctx = context as unknown as Record<string, unknown>;
331+
const ctx = context as unknown as typeof mockContext;
329332
expect(ctx.Attachment).toBeInstanceOf(Function);
330333
expect(ctx.editing).toBe(false);
331334
expect(ctx.messages).toBeInstanceOf(Array);
@@ -461,11 +464,7 @@ describe('Channel initial load useEffect', () => {
461464
await waitFor(() => expect(Object.keys(channelState.current.state.members!)).toHaveLength(10));
462465
});
463466

464-
function getElementsAround<T extends Record<string, unknown>>(
465-
array: T[],
466-
key: keyof T,
467-
id: unknown,
468-
) {
467+
function getElementsAround<T extends object>(array: T[], key: keyof T, id: unknown) {
469468
const index = array.findIndex((obj) => obj[key] === id);
470469

471470
if (index === -1) {
@@ -490,7 +489,7 @@ describe('Channel initial load useEffect', () => {
490489

491490
const loadMessageIntoState = jest.fn(() => {
492491
const newMessages = getElementsAround(
493-
messages as unknown as Record<string, unknown>[],
492+
messages as unknown as typeof channel.state.messages,
494493
'id',
495494
messageToSearch.id,
496495
);
@@ -532,7 +531,9 @@ describe('Channel initial load useEffect', () => {
532531
jest.restoreAllMocks();
533532
cleanup();
534533
});
535-
const mockedHook = (values: Record<string, unknown>) =>
534+
const mockedHook = (
535+
values: Partial<ReturnType<typeof MessageListPaginationHooks.useMessageListPagination>>,
536+
) =>
536537
jest.spyOn(MessageListPaginationHooks, 'useMessageListPagination').mockImplementation(
537538
() =>
538539
({
@@ -556,12 +557,12 @@ describe('Channel initial load useEffect', () => {
556557
const channel = chatClient.channel('messaging', mockedChannel.channel.id);
557558
await channel.watch();
558559
const user = generateUser();
559-
const read_data: Record<string, unknown> = {};
560+
const read_data: typeof channel.state.read = {};
560561

561562
read_data[chatClient.user!.id] = {
562563
last_read: new Date(),
563564
user,
564-
};
565+
} as unknown as (typeof channel.state.read)[string];
565566

566567
channel.state = {
567568
...channelInitialState,
@@ -591,7 +592,7 @@ describe('Channel initial load useEffect', () => {
591592

592593
const user = generateUser();
593594
const numberOfUnreadMessages = 15;
594-
const read_data: Record<string, unknown> = {};
595+
const read_data: typeof channel.state.read = {};
595596

596597
read_data[chatClient.user!.id] = {
597598
last_read: new Date(),
@@ -626,7 +627,7 @@ describe('Channel initial load useEffect', () => {
626627

627628
const user = generateUser();
628629
const numberOfUnreadMessages = 2;
629-
const read_data: Record<string, unknown> = {};
630+
const read_data: typeof channel.state.read = {};
630631

631632
read_data[chatClient.user!.id] = {
632633
last_read: new Date(),

package/src/components/Channel/__tests__/useMessageListPagination.test.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ describe('useMessageListPagination', () => {
1515
let chatClient: StreamChat;
1616
let channel: ChannelType;
1717

18-
const mockedHook = (state: Record<string, unknown>, values?: Record<string, unknown>) =>
18+
const mockedHook = (
19+
state: Partial<typeof channelInitialState>,
20+
values?: Partial<ReturnType<typeof ChannelStateHooks.useChannelMessageDataState>>,
21+
) =>
1922
jest.spyOn(ChannelStateHooks, 'useChannelMessageDataState').mockImplementation(
2023
() =>
2124
({

package/src/components/ChannelList/__tests__/ChannelList.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ describe('ChannelList', () => {
215215
const staleChannel = [createMockChannel('stale-channel')];
216216
const freshChannel = [createMockChannel('new-channel')];
217217
const spy = jest.spyOn(chatClient, 'queryChannels');
218-
spy.mockImplementation(((filters: Record<string, unknown> = {}) => {
218+
spy.mockImplementation(((filters: Parameters<typeof chatClient.queryChannels>[0] = {}) => {
219219
if (Object.prototype.hasOwnProperty.call(filters, 'new-filter')) {
220220
return deferredCallForFreshFilter.promise;
221221
}

package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { type ComponentProps } from 'react';
22
import { Text } from 'react-native';
33

44
import { render } from '@testing-library/react-native';
@@ -7,16 +7,19 @@ import type { Channel } from 'stream-chat';
77
import { ThemeProvider, defaultTheme } from '../../../contexts';
88
import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext';
99
import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelActionItems';
10+
import { StreamBottomSheetModalFlatList } from '../../UIComponents/StreamBottomSheetModalFlatList';
1011
import type { ChannelDetailsHeaderProps } from '../ChannelDetailsBottomSheet';
1112
import { ChannelDetailsBottomSheet } from '../ChannelDetailsBottomSheet';
1213

14+
type StreamBottomSheetModalFlatListProps = ComponentProps<typeof StreamBottomSheetModalFlatList>;
15+
1316
const mockStreamBottomSheetModalFlatList = jest.fn(
1417
// eslint-disable-next-line @typescript-eslint/no-unused-vars
15-
(_props: Record<string, unknown>) => null,
18+
(_props: StreamBottomSheetModalFlatListProps) => null,
1619
);
1720

1821
jest.mock('../../UIComponents/StreamBottomSheetModalFlatList', () => ({
19-
StreamBottomSheetModalFlatList: (...args: [Record<string, unknown>]) =>
22+
StreamBottomSheetModalFlatList: (...args: [StreamBottomSheetModalFlatListProps]) =>
2023
mockStreamBottomSheetModalFlatList(...args),
2124
}));
2225

@@ -77,7 +80,9 @@ describe('ChannelDetailsBottomSheet', () => {
7780

7881
expect(mockStreamBottomSheetModalFlatList).toHaveBeenCalled();
7982
const flatListProps = (
80-
mockStreamBottomSheetModalFlatList.mock.calls[0] as unknown as [Record<string, unknown>]
83+
mockStreamBottomSheetModalFlatList.mock.calls[0] as unknown as [
84+
StreamBottomSheetModalFlatListProps,
85+
]
8186
)?.[0];
8287
expect(flatListProps).toEqual(
8388
expect.objectContaining({

package/src/components/ChannelPreview/__tests__/ChannelPreview.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const ChannelPreviewUIComponent = (props: ChannelPreviewUIComponentProps) => {
5757

5858
const initChannelFromData = async (
5959
chatClient: StreamChat,
60-
overrides: Record<string, unknown> = {},
60+
overrides: Parameters<typeof generateChannelResponse>[0] = {},
6161
) => {
6262
const mockedChannel = generateChannelResponse(overrides);
6363
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
@@ -98,14 +98,14 @@ describe('ChannelPreview', () => {
9898
);
9999
};
100100

101-
const generateChannelWrapper = (overrides: Record<string, unknown>) =>
101+
const generateChannelWrapper = (overrides: Partial<Channel>) =>
102102
generateChannel({
103103
countUnread: jest.fn().mockReturnValue(0),
104104
initialized: true,
105105
lastMessage: jest.fn().mockReturnValue(generateMessage()),
106106
muteStatus: jest.fn().mockReturnValue({ muted: false }),
107107
...overrides,
108-
});
108+
} as unknown as Parameters<typeof generateChannel>[0]);
109109

110110
const useInitializeChannel = async (c: GetOrCreateChannelApiParams) => {
111111
useMockedApis(chatClient, [getOrCreateChannelApi(c)]);

package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { type ComponentProps } from 'react';
22
import { Text } from 'react-native';
33

44
import { act, render } from '@testing-library/react-native';
@@ -8,6 +8,7 @@ import { WithComponents } from '../../../contexts/componentsContext/ComponentsCo
88
import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelActionItems';
99
import * as ChannelActionItemsModule from '../../ChannelList/hooks/useChannelActionItems';
1010
import * as ChannelActionsModule from '../../ChannelList/hooks/useChannelActions';
11+
import { SwipableWrapper } from '../../UIComponents/SwipableWrapper';
1112
import { ChannelSwipableWrapper } from '../ChannelSwipableWrapper';
1213
import * as UseIsChannelMutedModule from '../hooks/useIsChannelMuted';
1314

@@ -60,7 +61,7 @@ jest.mock('../../UIComponents/SwipableWrapper', () => ({
6061
rightActionsProbe.items = items;
6162
return null;
6263
},
63-
SwipableWrapper: (...args: [React.PropsWithChildren<Record<string, unknown>>]) =>
64+
SwipableWrapper: (...args: [ComponentProps<typeof SwipableWrapper>]) =>
6465
mockSwipableWrapper(...args),
6566
}));
6667

package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { getTestClientWithUser } from '../../../../mock-builders/mock';
2323
import { Channel } from '../../../Channel/Channel';
2424
import { Chat } from '../../../Chat/Chat';
2525
import { Message } from '../../Message';
26+
import type { MessageFooterProps } from '../MessageFooter';
27+
import type { MessageHeaderProps } from '../MessageHeader';
2628

2729
describe('MessageContent', () => {
2830
let channel: ChannelType;
@@ -117,7 +119,7 @@ describe('MessageContent', () => {
117119
const user = generateUser();
118120
const message = generateMessage({ user });
119121

120-
const ContextMessageHeader = (props: Record<string, unknown>) => (
122+
const ContextMessageHeader = (props: MessageHeaderProps) => (
121123
<View {...props} testID='message-header' />
122124
);
123125

@@ -143,7 +145,7 @@ describe('MessageContent', () => {
143145
const user = generateUser();
144146
const message = generateMessage({ user });
145147

146-
const ContextMessageFooter = (props: Record<string, unknown>) => (
148+
const ContextMessageFooter = (props: MessageFooterProps) => (
147149
<View {...props} testID='message-footer' />
148150
);
149151

package/src/components/UIComponents/SwipableWrapper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const animationOptions = {
3232
export type SwipableActionItem = {
3333
action: () => void | Promise<void>;
3434
contentContainerStyle?: StyleProp<ViewStyle>;
35-
Content: React.ComponentType<Record<string, unknown>>;
35+
Content: React.ComponentType;
3636
id: string;
3737
};
3838

package/src/mock-builders/api/initiateClientWithChannels.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { generateMember } from '../generator/member';
88
import { generateUser } from '../generator/user';
99
import { getTestClientWithUser } from '../mock';
1010

11-
type ChannelData = Record<string, unknown>;
11+
type ChannelData = Parameters<typeof generateChannel>[0];
1212

1313
const initChannelFromData = async ({
1414
channelData,

0 commit comments

Comments
 (0)