Something went wrong. Try again.
Ghostty for the web with xterm.js API compatibility
Something went wrong. Try again.
12 kB · 417 lines
TypeScript
at testing
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418/** * Buffer API tests */
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';import { Terminal } from './terminal';
describe('Buffer API', () => { let term: Terminal | null = null; let container: HTMLElement | null = null;
beforeEach(async () => { // Create a container element if document is available if (typeof document !== 'undefined') { container = document.createElement('div'); document.body.appendChild(container); term = new Terminal({ cols: 80, rows: 24 }); await term.open(container); } });
afterEach(() => { if (term) { term.dispose(); term = null; } // Clean up container if (container && container.parentNode) { container.parentNode.removeChild(container); container = null; } });
describe('BufferNamespace', () => { test('should have buffer property', () => { expect(term.buffer).toBeDefined(); });
test('should have active, normal, and alternate buffers', () => { expect(term.buffer.active).toBeDefined(); expect(term.buffer.normal).toBeDefined(); expect(term.buffer.alternate).toBeDefined(); });
test('active buffer should be normal by default', () => { expect(term.buffer.active.type).toBe('normal'); });
test('should switch to alternate buffer', () => { // Enter alternate screen (smcup) term.write('\x1b[?1049h');
// Active buffer should now be alternate expect(term.buffer.active.type).toBe('alternate'); });
test('should switch back to normal buffer', () => { // Enter alternate screen term.write('\x1b[?1049h'); expect(term.buffer.active.type).toBe('alternate');
// Exit alternate screen (rmcup) term.write('\x1b[?1049l'); expect(term.buffer.active.type).toBe('normal'); }); });
describe('Buffer', () => { test('should have correct type', () => { expect(term.buffer.normal.type).toBe('normal'); expect(term.buffer.alternate.type).toBe('alternate'); });
test('should track cursor position', () => { term.write('Hello'); const buffer = term.buffer.active;
expect(buffer.cursorX).toBe(5); expect(buffer.cursorY).toBe(0); });
test('should track cursor position after newline', () => { term.write('Hello\r\nWorld'); const buffer = term.buffer.active;
expect(buffer.cursorX).toBe(5); expect(buffer.cursorY).toBe(1); });
test('should have correct length', () => { const buffer = term.buffer.normal; expect(buffer.length).toBeGreaterThanOrEqual(24); });
test('should return null cell', () => { const buffer = term.buffer.active; const nullCell = buffer.getNullCell();
expect(nullCell.getCode()).toBe(0); expect(nullCell.getChars()).toBe(''); expect(nullCell.getWidth()).toBe(1); }); });
describe('BufferLine', () => { test('should get line from buffer', () => { term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0);
expect(line).toBeDefined(); expect(line!.length).toBe(80); });
test('should return undefined for out of bounds line', () => { const buffer = term.buffer.active; const line = buffer.getLine(10000);
expect(line).toBeUndefined(); });
test('should have correct isWrapped flag', () => { // Write a short line (should not wrap) term.write('Short line'); const buffer = term.buffer.active; const line = buffer.getLine(0);
expect(line).toBeDefined(); expect(line!.isWrapped).toBe(false); });
test('translateToString should return line content', () => { term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); const text = line!.translateToString();
expect(text).toContain('Hello, World!'); });
test('translateToString should trim right when requested', () => { term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); const text = line!.translateToString(true);
expect(text).toBe('Hello'); expect(text.length).toBe(5); });
test('translateToString should respect startColumn and endColumn', () => { term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); const text = line!.translateToString(false, 7, 12);
expect(text).toBe('World'); }); });
describe('BufferCell', () => { test('should get cell from line', () => { term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell).toBeDefined(); });
test('should return undefined for out of bounds cell', () => { term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(200);
expect(cell).toBeUndefined(); });
test('getChars should return character', () => { term.write('H'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.getChars()).toBe('H'); });
test('getCode should return codepoint', () => { term.write('A'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.getCode()).toBe(65); // 'A' = 65 });
test('getWidth should return 1 for normal characters', () => { term.write('A'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.getWidth()).toBe(1); });
test('should detect bold text', () => { term.write('\x1b[1mBold\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isBold()).toBe(1); });
test('should detect italic text', () => { term.write('\x1b[3mItalic\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isItalic()).toBe(1); });
test('should detect underline text', () => { term.write('\x1b[4mUnderline\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isUnderline()).toBe(1); });
test('should detect strikethrough text', () => { term.write('\x1b[9mStrike\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isStrikethrough()).toBe(1); });
test('should detect blink text', () => { term.write('\x1b[5mBlink\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isBlink()).toBe(1); });
test('should detect inverse text', () => { term.write('\x1b[7mInverse\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isInverse()).toBe(1); });
test('should detect invisible text', () => { term.write('\x1b[8mInvisible\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isInvisible()).toBe(1); });
test('should detect faint text', () => { term.write('\x1b[2mFaint\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isFaint()).toBe(1); });
test('should return RGB foreground color', () => { term.write('\x1b[31mRed\x1b[0m'); // ANSI red const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
const color = cell!.getFgColor(); expect(color).toBeGreaterThan(0); });
test('should return RGB background color', () => { term.write('\x1b[41mRed BG\x1b[0m'); // ANSI red background const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
const color = cell!.getBgColor(); expect(color).toBeGreaterThan(0); });
test('empty cell should return empty string', () => { // Get a cell that was never written to const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(50); // Far from any written text
expect(cell!.getChars()).toBe(''); expect(cell!.getCode()).toBe(0); }); });
describe('Multi-line content', () => { test('should handle multiple lines correctly', () => { term.write('Line 1\r\n'); term.write('Line 2\r\n'); term.write('Line 3');
const buffer = term.buffer.active;
const line0 = buffer.getLine(0); const line1 = buffer.getLine(1); const line2 = buffer.getLine(2);
expect(line0!.translateToString(true)).toBe('Line 1'); expect(line1!.translateToString(true)).toBe('Line 2'); expect(line2!.translateToString(true)).toBe('Line 3'); });
test('should handle colored multi-line content', () => { term.write('\x1b[31mRed line\x1b[0m\r\n'); term.write('\x1b[32mGreen line\x1b[0m\r\n'); term.write('\x1b[34mBlue line\x1b[0m');
const buffer = term.buffer.active;
const line0 = buffer.getLine(0); const line1 = buffer.getLine(1); const line2 = buffer.getLine(2);
expect(line0!.translateToString(true)).toBe('Red line'); expect(line1!.translateToString(true)).toBe('Green line'); expect(line2!.translateToString(true)).toBe('Blue line');
// Check that first character of each line has correct style expect(line0!.getCell(0)!.getFgColor()).toBeGreaterThan(0); expect(line1!.getCell(0)!.getFgColor()).toBeGreaterThan(0); expect(line2!.getCell(0)!.getFgColor()).toBeGreaterThan(0); }); });
describe('Unicode support', () => { test('should handle emoji correctly', () => { term.write('😀'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.getChars()).toBe('😀'); expect(cell!.getCode()).toBe(0x1f600); // Emoji codepoint });
test('should handle accented characters', () => { term.write('Héllo'); const buffer = term.buffer.active; const line = buffer.getLine(0);
expect(line!.translateToString(true)).toBe('Héllo'); });
test('should handle various Unicode characters', () => { term.write('日本語'); const buffer = term.buffer.active; const line = buffer.getLine(0);
expect(line!.translateToString(true)).toBe('日本語'); }); });
describe('Edge cases', () => { test('should handle empty buffer', () => { const buffer = term.buffer.active; const line = buffer.getLine(0);
expect(line).toBeDefined(); expect(line!.translateToString(true)).toBe(''); });
test('should handle full line of text', () => { // Write exactly 80 characters const fullLine = 'A'.repeat(80); term.write(fullLine);
const buffer = term.buffer.active; const line = buffer.getLine(0);
expect(line!.translateToString(true)).toBe(fullLine); });
test('should handle cursor at end of line', () => { term.write('X'.repeat(80)); const buffer = term.buffer.active;
// Cursor stays at last column (79) until next character causes wrap // This is standard terminal behavior expect(buffer.cursorX).toBe(79); });
test('should handle multiple style attributes', () => { term.write('\x1b[1;3;4mBold+Italic+Underline\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); const cell = line!.getCell(0);
expect(cell!.isBold()).toBe(1); expect(cell!.isItalic()).toBe(1); expect(cell!.isUnderline()).toBe(1); }); });});