Fix bug where RadioButtonSelect treated an omitted isFocus parameter (#6274)

This commit is contained in:
Jacob Richman 2025-08-14 16:48:54 -07:00 committed by GitHub
parent a5c81e3fe0
commit 6037cb5d60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 87 additions and 26 deletions

View File

@ -92,11 +92,7 @@ export function IdeIntegrationNudge({
</Text>
<Text dimColor>{installText}</Text>
</Box>
<RadioButtonSelect
items={OPTIONS}
onSelect={onComplete}
isFocused={true}
/>
<RadioButtonSelect items={OPTIONS} onSelect={onComplete} />
</Box>
);
}

View File

@ -147,7 +147,6 @@ export function AuthDialog({
items={items}
initialIndex={initialAuthIndex}
onSelect={handleAuthSelect}
isFocused={true}
/>
</Box>
{errorMessage && (

View File

@ -5,11 +5,12 @@
*/
import { render } from 'ink-testing-library';
import { waitFor } from '@testing-library/react';
import {
RadioButtonSelect,
type RadioSelectItem,
} from './RadioButtonSelect.js';
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
const ITEMS: Array<RadioSelectItem<string>> = [
{ label: 'Option 1', value: 'one' },
@ -27,12 +28,7 @@ describe('<RadioButtonSelect />', () => {
it('renders with the second item selected and matches snapshot', () => {
const { lastFrame } = render(
<RadioButtonSelect
items={ITEMS}
initialIndex={1}
onSelect={() => {}}
isFocused={true}
/>,
<RadioButtonSelect items={ITEMS} initialIndex={1} onSelect={() => {}} />,
);
expect(lastFrame()).toMatchSnapshot();
});
@ -42,7 +38,6 @@ describe('<RadioButtonSelect />', () => {
<RadioButtonSelect
items={ITEMS}
onSelect={() => {}}
isFocused={true}
showNumbers={false}
/>,
);
@ -58,7 +53,6 @@ describe('<RadioButtonSelect />', () => {
<RadioButtonSelect
items={manyItems}
onSelect={() => {}}
isFocused={true}
showScrollArrows={true}
maxItemsToShow={5}
/>,
@ -82,11 +76,7 @@ describe('<RadioButtonSelect />', () => {
},
];
const { lastFrame } = render(
<RadioButtonSelect
items={themeItems}
onSelect={() => {}}
isFocused={true}
/>,
<RadioButtonSelect items={themeItems} onSelect={() => {}} />,
);
expect(lastFrame()).toMatchSnapshot();
});
@ -97,11 +87,7 @@ describe('<RadioButtonSelect />', () => {
value: `item-${i + 1}`,
}));
const { lastFrame } = render(
<RadioButtonSelect
items={manyItems}
onSelect={() => {}}
isFocused={true}
/>,
<RadioButtonSelect items={manyItems} onSelect={() => {}} />,
);
expect(lastFrame()).toMatchSnapshot();
});
@ -113,3 +99,83 @@ describe('<RadioButtonSelect />', () => {
expect(lastFrame()).toBe('');
});
});
describe('keyboard navigation', () => {
it('should call onSelect when "enter" is pressed', () => {
const onSelect = vi.fn();
const { stdin } = render(
<RadioButtonSelect items={ITEMS} onSelect={onSelect} />,
);
stdin.write('\r');
expect(onSelect).toHaveBeenCalledWith('one');
});
describe('when isFocused is false', () => {
it('should not handle any keyboard input', () => {
const onSelect = vi.fn();
const { stdin } = render(
<RadioButtonSelect
items={ITEMS}
onSelect={onSelect}
isFocused={false}
/>,
);
stdin.write('\u001B[B'); // Down arrow
stdin.write('\u001B[A'); // Up arrow
stdin.write('\r'); // Enter
expect(onSelect).not.toHaveBeenCalled();
});
});
describe.each([
{ description: 'when isFocused is true', isFocused: true },
{ description: 'when isFocused is omitted', isFocused: undefined },
])('$description', ({ isFocused }) => {
it('should navigate down with arrow key and select with enter', async () => {
const onSelect = vi.fn();
const { stdin, lastFrame } = render(
<RadioButtonSelect
items={ITEMS}
onSelect={onSelect}
isFocused={isFocused}
/>,
);
stdin.write('\u001B[B'); // Down arrow
await waitFor(() => {
expect(lastFrame()).toContain('● 2. Option 2');
});
stdin.write('\r');
expect(onSelect).toHaveBeenCalledWith('two');
});
it('should navigate up with arrow key and select with enter', async () => {
const onSelect = vi.fn();
const { stdin, lastFrame } = render(
<RadioButtonSelect
items={ITEMS}
onSelect={onSelect}
initialIndex={1}
isFocused={isFocused}
/>,
);
stdin.write('\u001B[A'); // Up arrow
await waitFor(() => {
expect(lastFrame()).toContain('● 1. Option 1');
});
stdin.write('\r');
expect(onSelect).toHaveBeenCalledWith('one');
});
});
});

View File

@ -55,7 +55,7 @@ export function RadioButtonSelect<T>({
initialIndex = 0,
onSelect,
onHighlight,
isFocused,
isFocused = true,
showScrollArrows = false,
maxItemsToShow = 10,
showNumbers = true,