Skip to content

Commit ccaea6a

Browse files
test(ThemeSettings): use stdin.write instead of inputHandlers array
The CI failure was in the test, not the wrap logic in `ThemeSettings`. `src/components/ThemeSettings.test.tsx:257` was asserting after calling a manually captured `useInput` handler. Under the current React 19 + Ink/Vitest setup, that bypasses Ink’s normal input/render cycle, so `setSelectedIndex(...)` never flushes in the test. The only recorded `onPreview` call is the mount effect, which is why CI saw `github-light` instead of the wrapped `solarized-dark`. Changed `src/components/ThemeSettings.test.tsx` to use Ink’s real stdin path with `stdin.write(KEY.UP|DOWN|ENTER|ESCAPE|CTRL_C)` instead of mocking `useInput`. That makes the tests exercise the component the same way the app does and removes the stale/racy behavior.
1 parent 0e85b05 commit ccaea6a

1 file changed

Lines changed: 25 additions & 69 deletions

File tree

src/components/ThemeSettings.test.tsx

Lines changed: 25 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { render } from 'ink-testing-library';
22

3-
import { THEME } from '../constants';
3+
import { KEY, THEME } from '../constants';
44
import { time } from '../utils';
55

66
interface MockCodeBlockProps {
@@ -15,38 +15,6 @@ interface MockSelectPromptHintProps {
1515
escapeLabel?: string;
1616
}
1717

18-
const { inputHandlers } = vi.hoisted(() => {
19-
const inputHandlers: ((
20-
input: string,
21-
key: {
22-
ctrl?: boolean;
23-
escape?: boolean;
24-
upArrow?: boolean;
25-
downArrow?: boolean;
26-
return?: boolean;
27-
},
28-
) => void)[] = [];
29-
return { inputHandlers };
30-
});
31-
32-
vi.mock('ink', async () => ({
33-
...(await vi.importActual('ink')),
34-
useInput: (
35-
handler: (
36-
input: string,
37-
key: {
38-
ctrl?: boolean;
39-
escape?: boolean;
40-
upArrow?: boolean;
41-
downArrow?: boolean;
42-
return?: boolean;
43-
},
44-
) => void,
45-
) => {
46-
inputHandlers.push(handler);
47-
},
48-
}));
49-
5018
vi.mock('./CodeBlock', async () => {
5119
const { Text } = await vi.importActual<typeof import('ink')>('ink');
5220
return {
@@ -68,10 +36,6 @@ vi.mock('./SelectPrompt', async () => {
6836
import { ThemeSettings } from './ThemeSettings';
6937

7038
describe('ThemeSettings', () => {
71-
beforeEach(() => {
72-
inputHandlers.length = 0;
73-
});
74-
7539
it('renders the current theme label and description', () => {
7640
const { lastFrame } = render(
7741
<ThemeSettings
@@ -131,7 +95,7 @@ describe('ThemeSettings', () => {
13195

13296
it('calls onClose when Escape is pressed', async () => {
13397
const onClose = vi.fn();
134-
render(
98+
const { stdin } = render(
13599
<ThemeSettings
136100
currentTheme="github-dark"
137101
onClose={onClose}
@@ -140,16 +104,15 @@ describe('ThemeSettings', () => {
140104
/>,
141105
);
142106

143-
const handler = inputHandlers.at(-1);
144-
handler?.('', { escape: true });
145-
await time.tick();
107+
stdin.write(KEY.ESCAPE);
108+
await time.tick(20);
146109

147110
expect(onClose).toHaveBeenCalledOnce();
148111
});
149112

150113
it('calls onClose when Ctrl+C is pressed', async () => {
151114
const onClose = vi.fn();
152-
render(
115+
const { stdin } = render(
153116
<ThemeSettings
154117
currentTheme="github-dark"
155118
onClose={onClose}
@@ -158,16 +121,15 @@ describe('ThemeSettings', () => {
158121
/>,
159122
);
160123

161-
const handler = inputHandlers.at(-1);
162-
handler?.('c', { ctrl: true });
163-
await time.tick();
124+
stdin.write(KEY.CTRL_C);
125+
await time.tick(20);
164126

165127
expect(onClose).toHaveBeenCalledOnce();
166128
});
167129

168130
it('calls onSave with selected theme when Enter is pressed', async () => {
169131
const onSave = vi.fn();
170-
render(
132+
const { stdin } = render(
171133
<ThemeSettings
172134
currentTheme="nord"
173135
onClose={vi.fn()}
@@ -176,16 +138,15 @@ describe('ThemeSettings', () => {
176138
/>,
177139
);
178140

179-
const handler = inputHandlers.at(-1);
180-
handler?.('', { return: true });
181-
await time.tick();
141+
stdin.write(KEY.ENTER);
142+
await time.tick(20);
182143

183144
expect(onSave).toHaveBeenCalledWith('nord');
184145
});
185146

186147
it('moves selection down with down arrow and calls onPreview', async () => {
187148
const onPreview = vi.fn();
188-
render(
149+
const { stdin } = render(
189150
<ThemeSettings
190151
currentTheme="github-dark"
191152
onClose={vi.fn()}
@@ -194,17 +155,16 @@ describe('ThemeSettings', () => {
194155
/>,
195156
);
196157

197-
const handler = inputHandlers.at(-1);
198-
handler?.('', { downArrow: true });
199-
await time.tick(10);
158+
stdin.write(KEY.DOWN);
159+
await time.tick(20);
200160

201161
const nextThemeId = THEME.LIST[2]?.id ?? THEME.LIST[0].id;
202162
expect(onPreview).toHaveBeenCalledWith(nextThemeId);
203163
});
204164

205165
it('moves selection up with up arrow', async () => {
206166
const onPreview = vi.fn();
207-
render(
167+
const { stdin } = render(
208168
<ThemeSettings
209169
currentTheme="github-dark"
210170
onClose={vi.fn()}
@@ -213,17 +173,16 @@ describe('ThemeSettings', () => {
213173
/>,
214174
);
215175

216-
const handler = inputHandlers.at(-1);
217-
handler?.('', { upArrow: true });
218-
await time.tick(10);
176+
stdin.write(KEY.UP);
177+
await time.tick(20);
219178

220179
expect(onPreview).toHaveBeenCalledWith(THEME.LIST[0].id);
221180
});
222181

223182
it('wraps down arrow from last to first', async () => {
224183
const lastThemeId = THEME.LIST[THEME.LIST.length - 1].id;
225184
const onPreview = vi.fn();
226-
render(
185+
const { stdin } = render(
227186
<ThemeSettings
228187
currentTheme={lastThemeId}
229188
onClose={vi.fn()}
@@ -232,16 +191,15 @@ describe('ThemeSettings', () => {
232191
/>,
233192
);
234193

235-
const handler = inputHandlers.at(-1);
236-
handler?.('', { downArrow: true });
237-
await time.tick(10);
194+
stdin.write(KEY.DOWN);
195+
await time.tick(20);
238196

239197
expect(onPreview).toHaveBeenCalledWith(THEME.LIST[0].id);
240198
});
241199

242200
it('wraps up arrow from first to last', async () => {
243201
const onPreview = vi.fn();
244-
render(
202+
const { stdin } = render(
245203
<ThemeSettings
246204
currentTheme="github-light"
247205
onClose={vi.fn()}
@@ -250,9 +208,8 @@ describe('ThemeSettings', () => {
250208
/>,
251209
);
252210

253-
const handler = inputHandlers.at(-1);
254-
handler?.('', { upArrow: true });
255-
await time.tick(10);
211+
stdin.write(KEY.UP);
212+
await time.tick(20);
256213

257214
expect(onPreview).toHaveBeenCalledWith(
258215
THEME.LIST[THEME.LIST.length - 1].id,
@@ -262,7 +219,7 @@ describe('ThemeSettings', () => {
262219
it('ignores unrecognized key input', async () => {
263220
const onClose = vi.fn();
264221
const onSave = vi.fn();
265-
render(
222+
const { stdin } = render(
266223
<ThemeSettings
267224
currentTheme="github-dark"
268225
onClose={onClose}
@@ -271,9 +228,8 @@ describe('ThemeSettings', () => {
271228
/>,
272229
);
273230

274-
const handler = inputHandlers.at(-1);
275-
handler?.('a', {});
276-
await time.tick();
231+
stdin.write('a');
232+
await time.tick(20);
277233

278234
expect(onClose).not.toHaveBeenCalled();
279235
expect(onSave).not.toHaveBeenCalled();

0 commit comments

Comments
 (0)