Files
openchamber/packages/web/server/lib/terminal/output-replay-buffer.test.js
T
Isaac Sanchez-HawkinsandIsaac Sanchez 323347f50e fix(terminal): preserve UTF-8 replay chunks (#1181)
* fix(terminal): preserve UTF-8 replay chunks

* fix(terminal): avoid replay trim prepends

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-12 11:05:03 +03:00

76 lines
2.8 KiB
JavaScript

import { describe, expect, it } from 'vitest';
import {
TERMINAL_OUTPUT_REPLAY_MAX_BYTES,
appendTerminalOutputReplayChunk,
createTerminalOutputReplayBuffer,
getLatestTerminalOutputReplayChunkId,
listTerminalOutputReplayChunksSince,
} from './output-replay-buffer.js';
describe('terminal output replay buffer', () => {
it('starts empty', () => {
const bufferState = createTerminalOutputReplayBuffer();
expect(bufferState).toEqual({ chunks: [], totalBytes: 0, nextId: 1 });
expect(getLatestTerminalOutputReplayChunkId(bufferState)).toBe(0);
});
it('appends chunks with incrementing ids', () => {
const bufferState = createTerminalOutputReplayBuffer();
const first = appendTerminalOutputReplayChunk(bufferState, 'prompt> ');
const second = appendTerminalOutputReplayChunk(bufferState, 'ls\r\n');
expect(first).toEqual({ id: 1, data: 'prompt> ', bytes: 8 });
expect(second).toEqual({ id: 2, data: 'ls\r\n', bytes: 4 });
expect(getLatestTerminalOutputReplayChunkId(bufferState)).toBe(2);
});
it('lists chunks after a replay cursor', () => {
const bufferState = createTerminalOutputReplayBuffer();
appendTerminalOutputReplayChunk(bufferState, 'prompt> ');
appendTerminalOutputReplayChunk(bufferState, 'ls\r\n');
appendTerminalOutputReplayChunk(bufferState, 'file.txt\r\n');
expect(listTerminalOutputReplayChunksSince(bufferState, 1).map((chunk) => chunk.data)).toEqual([
'ls\r\n',
'file.txt\r\n',
]);
});
it('trims old chunks beyond max bytes', () => {
const bufferState = createTerminalOutputReplayBuffer();
appendTerminalOutputReplayChunk(bufferState, '1234', 8);
appendTerminalOutputReplayChunk(bufferState, '5678', 8);
appendTerminalOutputReplayChunk(bufferState, '90', 8);
expect(bufferState.chunks.map((chunk) => chunk.data)).toEqual(['5678', '90']);
expect(bufferState.totalBytes).toBe(6);
});
it('trims oversized single chunks to the configured max bytes', () => {
const bufferState = createTerminalOutputReplayBuffer();
const chunk = appendTerminalOutputReplayChunk(bufferState, 'abcdefghij', 4);
expect(chunk?.data).toBe('ghij');
expect(chunk?.bytes).toBe(4);
expect(bufferState.totalBytes).toBe(4);
});
it('does not split multibyte characters when trimming oversized chunks', () => {
const bufferState = createTerminalOutputReplayBuffer();
const chunk = appendTerminalOutputReplayChunk(bufferState, '🙂x', 2);
expect(chunk?.data).toBe('x');
expect(chunk?.bytes).toBe(1);
expect(bufferState.totalBytes).toBe(1);
});
it('uses the default max bytes when not provided', () => {
const bufferState = createTerminalOutputReplayBuffer();
const chunk = appendTerminalOutputReplayChunk(bufferState, 'ok');
expect(chunk?.bytes).toBe(2);
expect(TERMINAL_OUTPUT_REPLAY_MAX_BYTES).toBe(64 * 1024);
});
});