Migrate core render util to use xterm.js as part of the rendering loop. (#19044)

This commit is contained in:
Jacob Richman
2026-02-18 16:46:50 -08:00
committed by GitHub
parent 04c52513e7
commit 04f65f3d55
213 changed files with 7065 additions and 3852 deletions

View File

@@ -24,8 +24,6 @@ vi.mock('../../contexts/UIStateContext.js', () => ({
})),
}));
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
describe('<VirtualizedList />', () => {
const keyExtractor = (item: string) => item;
@@ -60,7 +58,7 @@ describe('<VirtualizedList />', () => {
])(
'renders only visible items ($name)',
async ({ initialScrollIndex, visible, notVisible }) => {
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = render(
<Box height={10} width={100} borderStyle="round">
<VirtualizedList
data={longData}
@@ -71,9 +69,7 @@ describe('<VirtualizedList />', () => {
/>
</Box>,
);
await act(async () => {
await delay(0);
});
await waitUntilReady();
const frame = lastFrame();
visible.forEach((item) => {
@@ -83,11 +79,12 @@ describe('<VirtualizedList />', () => {
expect(frame).not.toContain(item);
});
expect(frame).toMatchSnapshot();
unmount();
},
);
it('sticks to bottom when new items added', async () => {
const { lastFrame, rerender } = render(
const { lastFrame, rerender, waitUntilReady, unmount } = render(
<Box height={10} width={100} borderStyle="round">
<VirtualizedList
data={longData}
@@ -98,38 +95,37 @@ describe('<VirtualizedList />', () => {
/>
</Box>,
);
await act(async () => {
await delay(0);
});
await waitUntilReady();
expect(lastFrame()).toContain('Item 99');
// Add items
const newData = [...longData, 'Item 100', 'Item 101'];
rerender(
<Box height={10} width={100} borderStyle="round">
<VirtualizedList
data={newData}
renderItem={renderItem1px}
keyExtractor={keyExtractor}
estimatedItemHeight={() => itemHeight}
// We don't need to pass initialScrollIndex again for it to stick,
// but passing it doesn't hurt. The component should auto-stick because it was at bottom.
/>
</Box>,
);
await act(async () => {
await delay(0);
rerender(
<Box height={10} width={100} borderStyle="round">
<VirtualizedList
data={newData}
renderItem={renderItem1px}
keyExtractor={keyExtractor}
estimatedItemHeight={() => itemHeight}
// We don't need to pass initialScrollIndex again for it to stick,
// but passing it doesn't hurt. The component should auto-stick because it was at bottom.
/>
</Box>,
);
});
await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('Item 101');
expect(frame).not.toContain('Item 0');
unmount();
});
it('scrolls down to show new items when requested via ref', async () => {
const ref = createRef<VirtualizedListRef<string>>();
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = render(
<Box height={10} width={100} borderStyle="round">
<VirtualizedList
ref={ref}
@@ -140,20 +136,19 @@ describe('<VirtualizedList />', () => {
/>
</Box>,
);
await act(async () => {
await delay(0);
});
await waitUntilReady();
expect(lastFrame()).toContain('Item 0');
// Scroll to bottom via ref
await act(async () => {
ref.current?.scrollToEnd();
await delay(0);
});
await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('Item 99');
unmount();
});
it.each([
@@ -184,7 +179,7 @@ describe('<VirtualizedList />', () => {
(_, i) => `Item ${i}`,
);
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = render(
<Box height={20} width={100} borderStyle="round">
<VirtualizedList
data={veryLongData}
@@ -197,13 +192,12 @@ describe('<VirtualizedList />', () => {
/>
</Box>,
);
await act(async () => {
await delay(0);
});
await waitUntilReady();
const frame = lastFrame();
expect(mountedCount).toBe(expectedMountedCount);
expect(frame).toMatchSnapshot();
unmount();
},
);
});
@@ -267,10 +261,8 @@ describe('<VirtualizedList />', () => {
return null;
};
const { lastFrame } = render(<TestComponent />);
await act(async () => {
await delay(0);
});
const { lastFrame, waitUntilReady, unmount } = render(<TestComponent />);
await waitUntilReady();
// Initially, only Item 0 (height 10) fills the 10px viewport
expect(lastFrame()).toContain('Item 0');
@@ -279,13 +271,14 @@ describe('<VirtualizedList />', () => {
// Shrink Item 0 to 1px via context
await act(async () => {
setHeightFn(1);
await delay(0);
});
await waitUntilReady();
// Now Item 0 is 1px, so Items 1-9 should also be visible to fill 10px
expect(lastFrame()).toContain('Item 0');
expect(lastFrame()).toContain('Item 1');
expect(lastFrame()).toContain('Item 9');
unmount();
});
it('updates scroll position correctly when scrollBy is called multiple times in the same tick', async () => {
@@ -299,7 +292,7 @@ describe('<VirtualizedList />', () => {
);
const keyExtractor = (item: string) => item;
render(
const { waitUntilReady, unmount } = render(
<Box height={10} width={100} borderStyle="round">
<VirtualizedList
ref={ref}
@@ -310,26 +303,25 @@ describe('<VirtualizedList />', () => {
/>
</Box>,
);
await act(async () => {
await delay(0);
});
await waitUntilReady();
expect(ref.current?.getScrollState().scrollTop).toBe(0);
await act(async () => {
ref.current?.scrollBy(1);
ref.current?.scrollBy(1);
await delay(0);
});
await waitUntilReady();
expect(ref.current?.getScrollState().scrollTop).toBe(2);
await act(async () => {
ref.current?.scrollBy(2);
await delay(0);
});
await waitUntilReady();
expect(ref.current?.getScrollState().scrollTop).toBe(4);
unmount();
});
it('renders correctly in copyModeEnabled when scrolled', async () => {
@@ -340,7 +332,7 @@ describe('<VirtualizedList />', () => {
const longData = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
// Use copy mode
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = render(
<Box height={10} width={100}>
<VirtualizedList
data={longData}
@@ -355,9 +347,7 @@ describe('<VirtualizedList />', () => {
/>
</Box>,
);
await act(async () => {
await delay(0);
});
await waitUntilReady();
// Item 50 should be visible
expect(lastFrame()).toContain('Item 50');
@@ -365,5 +355,6 @@ describe('<VirtualizedList />', () => {
expect(lastFrame()).toContain('Item 59');
// But far away items should not be (ensures we are actually scrolled)
expect(lastFrame()).not.toContain('Item 0');
unmount();
});
});