mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-20 02:51:55 -07:00
Support ink scrolling final pr (#12567)
This commit is contained in:
@@ -12,6 +12,8 @@ import { ScrollProvider } from '../../contexts/ScrollProvider.js';
|
||||
import { KeypressProvider } from '../../contexts/KeypressContext.js';
|
||||
import { MouseProvider } from '../../contexts/MouseContext.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { waitFor } from '../../../test-utils/async.js';
|
||||
|
||||
// Mock useStdout to provide a fixed size for testing
|
||||
vi.mock('ink', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('ink')>();
|
||||
@@ -152,28 +154,20 @@ describe('ScrollableList Demo Behavior', () => {
|
||||
await act(async () => {
|
||||
addItem?.();
|
||||
});
|
||||
for (let i = 0; i < 20; i++) {
|
||||
if (lastFrame!()?.includes('Count: 1001')) break;
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
});
|
||||
}
|
||||
await waitFor(() => {
|
||||
expect(lastFrame!()).toContain('Count: 1001');
|
||||
});
|
||||
expect(lastFrame!()).toContain('Item 1001');
|
||||
expect(lastFrame!()).toContain('Count: 1001');
|
||||
expect(lastFrame!()).not.toContain('Item 990'); // Should have scrolled past it
|
||||
|
||||
// Add item 1002
|
||||
await act(async () => {
|
||||
addItem?.();
|
||||
});
|
||||
for (let i = 0; i < 20; i++) {
|
||||
if (lastFrame!()?.includes('Count: 1002')) break;
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
});
|
||||
}
|
||||
await waitFor(() => {
|
||||
expect(lastFrame!()).toContain('Count: 1002');
|
||||
});
|
||||
expect(lastFrame!()).toContain('Item 1002');
|
||||
expect(lastFrame!()).toContain('Count: 1002');
|
||||
expect(lastFrame!()).not.toContain('Item 991');
|
||||
|
||||
// Scroll up directly via ref
|
||||
@@ -188,13 +182,103 @@ describe('ScrollableList Demo Behavior', () => {
|
||||
await act(async () => {
|
||||
addItem?.();
|
||||
});
|
||||
for (let i = 0; i < 20; i++) {
|
||||
if (lastFrame!()?.includes('Count: 1003')) break;
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
});
|
||||
}
|
||||
await waitFor(() => {
|
||||
expect(lastFrame!()).toContain('Count: 1003');
|
||||
});
|
||||
expect(lastFrame!()).not.toContain('Item 1003');
|
||||
expect(lastFrame!()).toContain('Count: 1003');
|
||||
});
|
||||
|
||||
it('should display sticky header when scrolled past the item', async () => {
|
||||
let listRef: ScrollableListRef<Item> | null = null;
|
||||
const StickyTestComponent = () => {
|
||||
const items = Array.from({ length: 100 }, (_, i) => ({
|
||||
id: String(i),
|
||||
title: `Item ${i + 1}`,
|
||||
}));
|
||||
|
||||
const ref = useRef<ScrollableListRef<Item>>(null);
|
||||
useEffect(() => {
|
||||
listRef = ref.current;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MouseProvider mouseEventsEnabled={false}>
|
||||
<KeypressProvider>
|
||||
<ScrollProvider>
|
||||
<Box flexDirection="column" width={80} height={10}>
|
||||
<ScrollableList
|
||||
ref={ref}
|
||||
data={items}
|
||||
renderItem={({ item, index }) => (
|
||||
<Box flexDirection="column" height={3}>
|
||||
{index === 0 ? (
|
||||
<Box
|
||||
sticky
|
||||
stickyChildren={<Text>[STICKY] {item.title}</Text>}
|
||||
>
|
||||
<Text>[Normal] {item.title}</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<Text>[Normal] {item.title}</Text>
|
||||
)}
|
||||
<Text>Content for {item.title}</Text>
|
||||
<Text>More content for {item.title}</Text>
|
||||
</Box>
|
||||
)}
|
||||
estimatedItemHeight={() => 3}
|
||||
keyExtractor={(item) => item.id}
|
||||
hasFocus={true}
|
||||
/>
|
||||
</Box>
|
||||
</ScrollProvider>
|
||||
</KeypressProvider>
|
||||
</MouseProvider>
|
||||
);
|
||||
};
|
||||
|
||||
let lastFrame: () => string | undefined;
|
||||
await act(async () => {
|
||||
const result = render(<StickyTestComponent />);
|
||||
lastFrame = result.lastFrame;
|
||||
});
|
||||
|
||||
// Initially at top, should see Normal Item 1
|
||||
await waitFor(() => {
|
||||
expect(lastFrame!()).toContain('[Normal] Item 1');
|
||||
});
|
||||
expect(lastFrame!()).not.toContain('[STICKY] Item 1');
|
||||
|
||||
// Scroll down slightly. Item 1 (height 3) is now partially off-screen (-2), so it should stick.
|
||||
await act(async () => {
|
||||
listRef?.scrollBy(2);
|
||||
});
|
||||
|
||||
// Now Item 1 should be stuck
|
||||
await waitFor(() => {
|
||||
expect(lastFrame!()).toContain('[STICKY] Item 1');
|
||||
});
|
||||
expect(lastFrame!()).not.toContain('[Normal] Item 1');
|
||||
|
||||
// Scroll further down to unmount Item 1.
|
||||
// Viewport height 10, item height 3. Scroll to 10.
|
||||
// startIndex should be around 2, so Item 1 (index 0) is unmounted.
|
||||
await act(async () => {
|
||||
listRef?.scrollTo(10);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(lastFrame!()).not.toContain('[STICKY] Item 1');
|
||||
});
|
||||
|
||||
// Scroll back to top
|
||||
await act(async () => {
|
||||
listRef?.scrollTo(0);
|
||||
});
|
||||
|
||||
// Should be normal again
|
||||
await waitFor(() => {
|
||||
expect(lastFrame!()).toContain('[Normal] Item 1');
|
||||
});
|
||||
expect(lastFrame!()).not.toContain('[STICKY] Item 1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -481,6 +481,7 @@ function VirtualizedList<T>(
|
||||
width="100%"
|
||||
height="100%"
|
||||
flexDirection="column"
|
||||
paddingRight={1}
|
||||
>
|
||||
<Box flexShrink={0} width="100%" flexDirection="column">
|
||||
<Box height={topSpacerHeight} flexShrink={0} />
|
||||
|
||||
Reference in New Issue
Block a user