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
@@ -62,7 +62,12 @@ const createConversation = (messages: MessageRecord[]): ConversationRecord => ({
});
describe('RewindViewer', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
@@ -87,18 +92,20 @@ describe('RewindViewer', () => {
},
],
},
])('renders $name', ({ messages }) => {
])('renders $name', async ({ messages }) => {
const conversation = createConversation(messages as MessageRecord[]);
const onExit = vi.fn();
const onRewind = vi.fn();
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={onExit}
onRewind={onRewind}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
@@ -113,13 +120,14 @@ describe('RewindViewer', () => {
]);
const onExit = vi.fn();
const onRewind = vi.fn();
const { lastFrame, stdin } = renderWithProviders(
const { lastFrame, stdin, waitUntilReady, unmount } = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={onExit}
onRewind={onRewind}
/>,
);
await waitUntilReady();
// Initial state
expect(lastFrame()).toMatchSnapshot('initial-state');
@@ -128,10 +136,12 @@ describe('RewindViewer', () => {
act(() => {
stdin.write('\x1b[B');
});
await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toMatchSnapshot('after-down');
});
unmount();
});
describe('Navigation', () => {
@@ -144,20 +154,30 @@ describe('RewindViewer', () => {
{ type: 'user', content: 'Q2', id: '2', timestamp: '1' },
{ type: 'user', content: 'Q3', id: '3', timestamp: '1' },
]);
const { lastFrame, stdin } = renderWithProviders(
const { lastFrame, stdin, waitUntilReady, unmount } = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={vi.fn()}
onRewind={vi.fn()}
/>,
);
await waitUntilReady();
act(() => {
stdin.write(sequence);
});
await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toMatchSnapshot(expectedSnapshot);
const frame = lastFrame();
expect(frame).toMatchSnapshot(expectedSnapshot);
if (expectedSnapshot === 'after-up') {
const headerLines = frame
?.split('\n')
.filter((line) => line.includes('╭───'));
expect(headerLines).toHaveLength(1);
}
});
unmount();
});
it('handles cyclic navigation', async () => {
@@ -166,18 +186,20 @@ describe('RewindViewer', () => {
{ type: 'user', content: 'Q2', id: '2', timestamp: '1' },
{ type: 'user', content: 'Q3', id: '3', timestamp: '1' },
]);
const { lastFrame, stdin } = renderWithProviders(
const { lastFrame, stdin, waitUntilReady, unmount } = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={vi.fn()}
onRewind={vi.fn()}
/>,
);
await waitUntilReady();
// Up from first -> Last
act(() => {
stdin.write('\x1b[A');
});
await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toMatchSnapshot('cyclic-up');
});
@@ -186,9 +208,11 @@ describe('RewindViewer', () => {
act(() => {
stdin.write('\x1b[B');
});
await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toMatchSnapshot('cyclic-down');
});
unmount();
});
});
@@ -199,14 +223,16 @@ describe('RewindViewer', () => {
actionStep: async (
stdin: { write: (data: string) => void },
lastFrame: () => string | undefined,
waitUntilReady: () => Promise<void>,
) => {
// Wait for confirmation dialog to be rendered and interactive
await waitFor(() => {
expect(lastFrame()).toContain('Confirm Rewind');
});
act(() => {
await act(async () => {
stdin.write('\r');
});
await waitUntilReady();
},
},
{
@@ -214,14 +240,18 @@ describe('RewindViewer', () => {
actionStep: async (
stdin: { write: (data: string) => void },
lastFrame: () => string | undefined,
waitUntilReady: () => Promise<void>,
) => {
// Wait for confirmation dialog
await waitFor(() => {
expect(lastFrame()).toContain('Confirm Rewind');
});
act(() => {
await act(async () => {
stdin.write('\x1b');
});
await act(async () => {
await waitUntilReady();
});
// Wait for return to main view
await waitFor(() => {
expect(lastFrame()).toContain('> Rewind');
@@ -233,23 +263,26 @@ describe('RewindViewer', () => {
{ type: 'user', content: 'Original Prompt', id: '1', timestamp: '1' },
]);
const onRewind = vi.fn();
const { lastFrame, stdin } = renderWithProviders(
const { lastFrame, stdin, waitUntilReady, unmount } = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={vi.fn()}
onRewind={onRewind}
/>,
);
await waitUntilReady();
// Select
act(() => {
await act(async () => {
stdin.write('\x1b[A'); // Move up from 'Stay at current position'
stdin.write('\r');
});
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('confirmation-dialog');
// Act
await actionStep(stdin, lastFrame);
await actionStep(stdin, lastFrame, waitUntilReady);
unmount();
});
});
@@ -287,13 +320,14 @@ describe('RewindViewer', () => {
},
]);
const onRewind = vi.fn();
const { lastFrame, stdin } = renderWithProviders(
const { lastFrame, stdin, waitUntilReady, unmount } = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={vi.fn()}
onRewind={onRewind}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -302,6 +336,7 @@ describe('RewindViewer', () => {
stdin.write('\x1b[A'); // Move up from 'Stay at current position'
stdin.write('\r'); // Select
});
await waitUntilReady();
// Wait for confirmation dialog
await waitFor(() => {
@@ -312,14 +347,16 @@ describe('RewindViewer', () => {
act(() => {
stdin.write('\r');
});
await waitUntilReady();
await waitFor(() => {
expect(onRewind).toHaveBeenCalledWith('1', expected, expect.anything());
});
unmount();
});
});
it('updates content when conversation changes (background update)', () => {
it('updates content when conversation changes (background update)', async () => {
const messages: MessageRecord[] = [
{ type: 'user', content: 'Message 1', id: '1', timestamp: '1' },
];
@@ -327,13 +364,14 @@ describe('RewindViewer', () => {
const onExit = vi.fn();
const onRewind = vi.fn();
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={onExit}
onRewind={onRewind}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('initial');
@@ -345,14 +383,20 @@ describe('RewindViewer', () => {
];
conversation = createConversation(newMessages);
const { lastFrame: lastFrame2 } = renderWithProviders(
const {
lastFrame: lastFrame2,
waitUntilReady: waitUntilReady2,
unmount: unmount2,
} = renderWithProviders(
<RewindViewer
conversation={conversation}
onExit={onExit}
onRewind={onRewind}
/>,
);
await waitUntilReady2();
expect(lastFrame2()).toMatchSnapshot('after-update');
unmount2();
});
});