# 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483)

This commit is contained in:
tanzhenxin
2025-09-01 14:48:55 +08:00
committed by GitHub
parent 1610c1586e
commit 2572faf726
292 changed files with 19401 additions and 5941 deletions

View File

@@ -321,7 +321,7 @@ function visitBoxRow(element: React.ReactNode): Row {
const segment: StyledText = { text, props: parentProps ?? {} };
// Check the 'wrap' property from the merged props to decide the segment type.
if (parentProps === undefined || parentProps.wrap === 'wrap') {
if (parentProps === undefined || parentProps['wrap'] === 'wrap') {
hasSeenWrapped = true;
row.segments.push(segment);
} else {

View File

@@ -4,12 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from 'ink-testing-library';
import { renderWithProviders } from '../../../test-utils/render.js';
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' },
@@ -19,30 +20,24 @@ const ITEMS: Array<RadioSelectItem<string>> = [
describe('<RadioButtonSelect />', () => {
it('renders a list of items and matches snapshot', () => {
const { lastFrame } = render(
const { lastFrame } = renderWithProviders(
<RadioButtonSelect items={ITEMS} onSelect={() => {}} isFocused={true} />,
);
expect(lastFrame()).toMatchSnapshot();
});
it('renders with the second item selected and matches snapshot', () => {
const { lastFrame } = render(
<RadioButtonSelect
items={ITEMS}
initialIndex={1}
onSelect={() => {}}
isFocused={true}
/>,
const { lastFrame } = renderWithProviders(
<RadioButtonSelect items={ITEMS} initialIndex={1} onSelect={() => {}} />,
);
expect(lastFrame()).toMatchSnapshot();
});
it('renders with numbers hidden and matches snapshot', () => {
const { lastFrame } = render(
const { lastFrame } = renderWithProviders(
<RadioButtonSelect
items={ITEMS}
onSelect={() => {}}
isFocused={true}
showNumbers={false}
/>,
);
@@ -54,11 +49,10 @@ describe('<RadioButtonSelect />', () => {
label: `Item ${i + 1}`,
value: `item-${i + 1}`,
}));
const { lastFrame } = render(
const { lastFrame } = renderWithProviders(
<RadioButtonSelect
items={manyItems}
onSelect={() => {}}
isFocused={true}
showScrollArrows={true}
maxItemsToShow={5}
/>,
@@ -81,12 +75,8 @@ describe('<RadioButtonSelect />', () => {
themeTypeDisplay: '(Dark)',
},
];
const { lastFrame } = render(
<RadioButtonSelect
items={themeItems}
onSelect={() => {}}
isFocused={true}
/>,
const { lastFrame } = renderWithProviders(
<RadioButtonSelect items={themeItems} onSelect={() => {}} />,
);
expect(lastFrame()).toMatchSnapshot();
});
@@ -96,20 +86,96 @@ describe('<RadioButtonSelect />', () => {
label: `Item ${i + 1}`,
value: `item-${i + 1}`,
}));
const { lastFrame } = render(
<RadioButtonSelect
items={manyItems}
onSelect={() => {}}
isFocused={true}
/>,
const { lastFrame } = renderWithProviders(
<RadioButtonSelect items={manyItems} onSelect={() => {}} />,
);
expect(lastFrame()).toMatchSnapshot();
});
it('renders nothing when no items are provided', () => {
const { lastFrame } = render(
const { lastFrame } = renderWithProviders(
<RadioButtonSelect items={[]} onSelect={() => {}} isFocused={true} />,
);
expect(lastFrame()).toBe('');
});
});
describe('keyboard navigation', () => {
it('should call onSelect when "enter" is pressed', () => {
const onSelect = vi.fn();
const { stdin } = renderWithProviders(
<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 } = renderWithProviders(
<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 } = renderWithProviders(
<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 } = renderWithProviders(
<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,

View File

@@ -1833,6 +1833,13 @@ export function useTextBuffer({
}): void => {
const { sequence: input } = key;
if (key.paste) {
// Do not do any other processing on pastes so ensure we handle them
// before all other cases.
insert(input, { paste: key.paste });
return;
}
if (
key.name === 'return' ||
input === '\r' ||