Skip to content

Commit 277bc41

Browse files
authored
chore: improve type safety and clean up stale props in test files (#3070)
## Summary - Add type annotations to ~140 untyped `let` declarations across 30+ test files - Fix all 46 TS2345 errors (incomplete data passed where full types expected) using `fromPartial()` from `@total-typescript/shoehorn` - Replace 410 of 435 `as any` casts with proper types — `fromPartial<Type>()`, specific type assertions, or removed as unnecessary - Fix `markReadApi` mock builder bug: `last_read_message_id` was returning a full message object instead of a string ID, and `duration` was a number instead of a string - Remove stale props passed to components that no longer accept them: - `ChannelList`: `channel`, `setActiveChannel`, `additionalChannelSearchProps` - `ChannelListUI`: `LoadingErrorIndicator`, `LoadingIndicator` (moved to `ComponentProvider`) - `MessageList`: `hasMoreNewer`, `highlightedMessageId` (moved to `ChannelStateContext`) - `Message`: `getMessageActions`, `isMyMessage` (belong to `MessageContextValue`, not `MessageProps`) - `MessageUI`: `channelConfig` (already provided via `ChannelStateContext`) - `Thread`: `propName`, `read` in `additionalMessageListProps` - Fix ChannelList delete/hidden tests that were passing for the wrong reason (asserting on mount call instead of event call) ## Test plan - [x] `yarn lint-fix` passes (verified locally, enforced by pre-commit hook) - [ ] `yarn types` passes - [ ] `yarn test` passes — all existing tests remain green with no behavioral changes - [ ] Only 25 `as any` casts remain, all justified (browser global mocks like `window.ResizeObserver`, `navigator.mediaDevices`, and intentional invalid-input tests)
1 parent 55b1dd6 commit 277bc41

92 files changed

Lines changed: 1706 additions & 1286 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/components/Attachment/__tests__/Audio.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
3+
import { fromPartial } from '@total-typescript/shoehorn';
34

45
import { Audio } from '../Audio';
56
import {
@@ -126,7 +127,7 @@ describe('Audio', () => {
126127
const { getByTestId } = renderComponent({ og: audioAttachment });
127128
await clickToPlay();
128129
vi.spyOn(HTMLDivElement.prototype, 'getBoundingClientRect').mockImplementationOnce(
129-
() => ({ width: 120, x: 0 }) as any,
130+
() => fromPartial<DOMRect>({ width: 120, x: 0 }),
130131
);
131132

132133
vi.spyOn(HTMLAudioElement.prototype, 'currentTime', 'set').mockImplementationOnce(

src/components/Attachment/__tests__/Card.test.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
mockTranslationContextValue,
2626
useMockedApis,
2727
} from '../../../mock-builders';
28+
import type { GenerateChannelOptions } from '../../../mock-builders';
2829
import { WithAudioPlayback } from '../../AudioPlayback';
2930

3031
let chatClient: StreamChat;
@@ -36,11 +37,13 @@ vi.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation(() => {}
3637
vi.spyOn(window.HTMLMediaElement.prototype, 'load').mockImplementation(() => {});
3738
const channelActionContext = fromPartial<ChannelActionContextValue>({});
3839

39-
const mockedChannel = generateChannel({
40-
members: [generateMember({ user })],
41-
messages: [],
42-
threads: [],
43-
} as any);
40+
const mockedChannel = generateChannel(
41+
fromPartial<GenerateChannelOptions>({
42+
members: [generateMember({ user })],
43+
messages: [],
44+
threads: [],
45+
}),
46+
);
4447

4548
const renderCard = ({ cardProps, chatContext, theRenderer = render }: any) =>
4649
theRenderer(

src/components/Attachment/__tests__/Geolocation.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { act, render, screen } from '@testing-library/react';
2+
import { act, render, type RenderResult, screen } from '@testing-library/react';
33
import { Channel } from '../../Channel';
44
import { Chat } from '../../Chat';
55
import { Geolocation } from '../Geolocation';
@@ -43,7 +43,7 @@ const renderComponent = async (
4343
channels: [defaultChannel],
4444
client: defaultClient,
4545
} = await initClientWithChannels({ customUser: ownUser });
46-
let result;
46+
let result: RenderResult;
4747
await act(() => {
4848
result = render(
4949
<Chat client={client ?? defaultClient}>

src/components/Attachment/__tests__/VoiceRecording.test.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
3+
import { fromPartial } from '@total-typescript/shoehorn';
34

45
import {
56
generateMessage,
@@ -23,18 +24,20 @@ const attachment = generateVoiceRecordingAttachment();
2324

2425
(window as any).ResizeObserver = ResizeObserverMock;
2526

26-
vi.spyOn(HTMLDivElement.prototype, 'getBoundingClientRect').mockReturnValue({
27-
width: 120,
28-
} as any);
27+
vi.spyOn(HTMLDivElement.prototype, 'getBoundingClientRect').mockReturnValue(
28+
fromPartial<DOMRect>({
29+
width: 120,
30+
}),
31+
);
2932

3033
const clickPlay = async () => {
3134
await act(async () => {
3235
await fireEvent.click(screen.queryByTestId('play-audio'));
3336
});
3437
};
3538

36-
vi.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation(
37-
() => undefined as any,
39+
vi.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation(() =>
40+
Promise.resolve(),
3841
);
3942
vi.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation(() => {});
4043

@@ -111,8 +114,8 @@ describe('VoiceRecording', () => {
111114
describe('VoiceRecordingPlayer', () => {
112115
beforeAll(() => {
113116
vi.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation(() => {});
114-
vi.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation(
115-
() => undefined as any,
117+
vi.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation(() =>
118+
Promise.resolve(),
116119
);
117120
vi.spyOn(window.HTMLMediaElement.prototype, 'canPlayType').mockReturnValue('maybe');
118121
});

src/components/AudioPlayback/__tests__/AudioPlayer.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ describe('AudioPlayer', () => {
177177
const { onError, plugin } = makeErrorPlugin();
178178
const player = makePlayer({ plugins: [plugin] });
179179

180-
let resolve;
180+
let resolve: (value: void | PromiseLike<void>) => void;
181181
// attach and stub play to a pending promise
182182
player['ensureElementRef']();
183183
vi.spyOn(player.elementRef, 'play').mockImplementation(
@@ -205,7 +205,7 @@ describe('AudioPlayer', () => {
205205
const { onError, plugin } = makeErrorPlugin();
206206
const player = makePlayer({ plugins: [plugin] });
207207

208-
let resolve;
208+
let resolve: (value: void | PromiseLike<void>) => void;
209209
player['ensureElementRef']();
210210
vi.spyOn(player.elementRef, 'play').mockImplementation(
211211
() =>

src/components/AudioPlayback/__tests__/WithAudioPlayback.test.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
33
import { act, cleanup, render } from '@testing-library/react';
44

55
import { useAudioPlayer, WithAudioPlayback } from '../WithAudioPlayback';
6+
import type { AudioPlayer } from '../AudioPlayer';
67

78
// mock context used by WithAudioPlayback
89
vi.mock('../../../context', () => {
@@ -96,7 +97,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
9697
'allowConcurrentPlayback is %s',
9798
(allowConcurrentPlayback) => {
9899
it('useAudioPlayer returns undefined when src is missing', () => {
99-
let seen;
100+
let seen: AudioPlayer | undefined;
100101
renderWithProvider({
101102
allowConcurrentPlayback,
102103
ui: <RegisterPlayer onReady={(p) => (seen = p)} params={{}} />,
@@ -105,7 +106,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
105106
});
106107

107108
it('creates an AudioPlayer when src is provided and does not associate audio element until played', () => {
108-
let player;
109+
let player: AudioPlayer;
109110
renderWithProvider({
110111
allowConcurrentPlayback,
111112
ui: (
@@ -159,7 +160,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
159160
});
160161

161162
it('subscriptions: sets secondsElapsed and progress in state on timeupdate Event ', () => {
162-
let player;
163+
let player: AudioPlayer;
163164
renderWithProvider({
164165
allowConcurrentPlayback,
165166
ui: (
@@ -186,7 +187,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
186187
});
187188

188189
it('subscriptions: resets playback state on Event "ended"', () => {
189-
let player;
190+
let player: AudioPlayer;
190191
renderWithProvider({
191192
allowConcurrentPlayback,
192193
ui: (
@@ -221,7 +222,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
221222
});
222223

223224
it('subscriptions: error with MediaError.code=4 logs and sets canPlayRecord=false', () => {
224-
let player;
225+
let player: AudioPlayer;
225226
renderWithProvider({
226227
allowConcurrentPlayback,
227228
ui: (
@@ -261,7 +262,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
261262
});
262263

263264
it('registerError mapping: failed-to-start -> translated message and notification', () => {
264-
let player;
265+
let player: AudioPlayer;
265266
renderWithProvider({
266267
allowConcurrentPlayback,
267268
ui: (
@@ -284,7 +285,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
284285
});
285286

286287
it('registerError mapping: not-playable / seek-not-supported', () => {
287-
let player;
288+
let player: AudioPlayer;
288289
renderWithProvider({
289290
allowConcurrentPlayback,
290291
ui: (
@@ -311,7 +312,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
311312
});
312313

313314
it('registerError uses raw Error message if provided', () => {
314-
let player;
315+
let player: AudioPlayer;
315316
renderWithProvider({
316317
allowConcurrentPlayback,
317318
ui: (
@@ -331,7 +332,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
331332
});
332333

333334
it('unmounting WithAudioPlayback clears pool: element src is cleared and load() called', () => {
334-
let player;
335+
let player: AudioPlayer;
335336
const { unmount } = renderWithProvider({
336337
allowConcurrentPlayback,
337338
ui: (
@@ -354,7 +355,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
354355
});
355356

356357
it('unmounting WithAudioPlayback unsubscribes audio element listeners and pauses', () => {
357-
let player;
358+
let player: AudioPlayer;
358359
const { unmount } = renderWithProvider({
359360
allowConcurrentPlayback,
360361
ui: (
@@ -384,7 +385,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
384385
it('re-mounting provider with same props creates a fresh player and cleans previous element', () => {
385386
const params = { mimeType: 'audio/mpeg', src: 'https://example.com/a.mp3' };
386387

387-
let firstPlayer;
388+
let firstPlayer: AudioPlayer;
388389
const { unmount } = renderWithProvider({
389390
allowConcurrentPlayback,
390391
ui: <RegisterPlayer onReady={(p) => (firstPlayer = p)} params={params} />,
@@ -402,7 +403,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
402403
expect(firstPlayer.elementRef).toBeNull();
403404

404405
// New provider -> new pool -> new player + new <audio>
405-
let secondPlayer;
406+
let secondPlayer: AudioPlayer;
406407
renderWithProvider({
407408
allowConcurrentPlayback,
408409
ui: <RegisterPlayer onReady={(p) => (secondPlayer = p)} params={params} />,

src/components/AudioPlayback/plugins/__tests__/AudioPlayerNotificationsPlugin.test.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { fromPartial } from '@total-typescript/shoehorn';
2+
import type { StreamChat } from 'stream-chat';
3+
import type { TFunction } from 'i18next';
24
import { audioPlayerNotificationsPluginFactory } from '../AudioPlayerNotificationsPlugin';
35

46
describe('audioPlayerNotificationsPluginFactory', () => {
5-
const t = (s: any) => s;
7+
const t: TFunction = ((s: string) => s) as TFunction;
68

79
const makeClient = () => {
810
const addError = vi.fn();
@@ -15,8 +17,8 @@ describe('audioPlayerNotificationsPluginFactory', () => {
1517
it('reports mapped error messages for known errCodes', () => {
1618
const { addError, client } = makeClient();
1719
const plugin = audioPlayerNotificationsPluginFactory({
18-
client: client as any,
19-
t: t as any,
20+
client: fromPartial<StreamChat>(client),
21+
t,
2022
});
2123

2224
// simulate failed-to-start
@@ -44,8 +46,8 @@ describe('audioPlayerNotificationsPluginFactory', () => {
4446
it('falls back to provided Error if no errCode', () => {
4547
const { addError, client } = makeClient();
4648
const plugin = audioPlayerNotificationsPluginFactory({
47-
client: client as any,
48-
t: t as any,
49+
client: fromPartial<StreamChat>(client),
50+
t,
4951
});
5052

5153
plugin.onError?.({ error: new Error('X-Error'), player: fromPartial({}) });
@@ -57,8 +59,8 @@ describe('audioPlayerNotificationsPluginFactory', () => {
5759
it('falls back to generic message if no errCode and no Error', () => {
5860
const { addError, client } = makeClient();
5961
const plugin = audioPlayerNotificationsPluginFactory({
60-
client: client as any,
61-
t: t as any,
62+
client: fromPartial<StreamChat>(client),
63+
t,
6264
});
6365

6466
plugin.onError?.({ player: fromPartial({}) });

0 commit comments

Comments
 (0)