Something went wrong. Try again.
Ghostty for the web with xterm.js API compatibility
Something went wrong. Try again.
25 kB · 864 lines
TypeScript
at task8
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865/** * Comprehensive test suite for ScreenBuffer */
import { describe, test, expect } from 'bun:test';import { ScreenBuffer } from './buffer';
// ============================================================================// Basic Operations Tests// ============================================================================
describe('ScreenBuffer - Basic Operations', () => { test('initializes with correct dimensions', () => { const buffer = new ScreenBuffer(80, 24); const dims = buffer.getDimensions(); expect(dims.cols).toBe(80); expect(dims.rows).toBe(24); });
test('initializes cursor at origin', () => { const buffer = new ScreenBuffer(80, 24); const cursor = buffer.getCursor(); expect(cursor.x).toBe(0); expect(cursor.y).toBe(0); expect(cursor.visible).toBe(true); });
test('writes single character at cursor position', () => { const buffer = new ScreenBuffer(80, 24); buffer.writeChar('H'); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![0].width).toBe(1); });
test('writes multiple characters', () => { const buffer = new ScreenBuffer(80, 24); buffer.writeString('Hello'); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![1].char).toBe('e'); expect(line![2].char).toBe('l'); expect(line![3].char).toBe('l'); expect(line![4].char).toBe('o'); });
test('advances cursor after writing', () => { const buffer = new ScreenBuffer(80, 24); buffer.writeChar('A'); let cursor = buffer.getCursor(); expect(cursor.x).toBe(1); expect(cursor.y).toBe(0); buffer.writeChar('B'); cursor = buffer.getCursor(); expect(cursor.x).toBe(2); expect(cursor.y).toBe(0); });
test('applies current style to written characters', () => { const buffer = new ScreenBuffer(80, 24); buffer.setStyle({ bold: true, fg: { type: 'palette', index: 1 } }); buffer.writeChar('X'); const line = buffer.getLine(0); expect(line![0].bold).toBe(true); expect(line![0].fg).toEqual({ type: 'palette', index: 1 }); });});
// ============================================================================// Wide Characters Tests// ============================================================================
describe('ScreenBuffer - Wide Characters', () => { test('writes CJK character with width 2', () => { const buffer = new ScreenBuffer(80, 24); buffer.writeChar('中'); const line = buffer.getLine(0); expect(line![0].char).toBe('中'); expect(line![0].width).toBe(2); expect(line![1].width).toBe(0); // Padding cell });
test('advances cursor by 2 for wide character', () => { const buffer = new ScreenBuffer(80, 24); buffer.writeChar('日'); const cursor = buffer.getCursor(); expect(cursor.x).toBe(2); });
test('writes emoji with width 2', () => { const buffer = new ScreenBuffer(80, 24); buffer.writeChar('😀'); const line = buffer.getLine(0); expect(line![0].char).toBe('😀'); expect(line![0].width).toBe(2); });
test('wide char at edge wraps to next line', () => { const buffer = new ScreenBuffer(5, 24); buffer.writeString('AAAA'); // Fill to position 4 buffer.writeChar('中'); // Should wrap const cursor = buffer.getCursor(); expect(cursor.x).toBe(2); expect(cursor.y).toBe(1); const line = buffer.getLine(1); expect(line![0].char).toBe('中'); });
test('combining character appends to previous char', () => { const buffer = new ScreenBuffer(80, 24); buffer.writeChar('e'); buffer.writeChar('\u0301'); // Combining acute accent const line = buffer.getLine(0); // Should be normalized to é expect(line![0].char).toBe('e\u0301'.normalize('NFC')); expect(line![0].char.length).toBe(1); // Should be single composed char });});
// ============================================================================// Cursor Movement Tests// ============================================================================
describe('ScreenBuffer - Cursor Movement', () => { test('moves cursor to absolute position', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(10, 5); const cursor = buffer.getCursor(); expect(cursor.x).toBe(10); expect(cursor.y).toBe(5); });
test('clamps cursor to buffer bounds', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(100, 50); const cursor = buffer.getCursor(); expect(cursor.x).toBe(79); // cols - 1 expect(cursor.y).toBe(23); // rows - 1 });
test('moves cursor up', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(5, 10); buffer.moveCursorUp(3); const cursor = buffer.getCursor(); expect(cursor.y).toBe(7); });
test('moves cursor down', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(5, 10); buffer.moveCursorDown(3); const cursor = buffer.getCursor(); expect(cursor.y).toBe(13); });
test('moves cursor forward', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(5, 10); buffer.moveCursorForward(3); const cursor = buffer.getCursor(); expect(cursor.x).toBe(8); });
test('moves cursor backward', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(5, 10); buffer.moveCursorBackward(3); const cursor = buffer.getCursor(); expect(cursor.x).toBe(2); });
test('saves and restores cursor position', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(15, 8); buffer.saveCursor(); buffer.moveCursorTo(0, 0); expect(buffer.getCursor().x).toBe(0); buffer.restoreCursor(); const cursor = buffer.getCursor(); expect(cursor.x).toBe(15); expect(cursor.y).toBe(8); });});
// ============================================================================// Wrapping Tests// ============================================================================
describe('ScreenBuffer - Wrapping', () => { test('wraps to next line at right edge', () => { const buffer = new ScreenBuffer(5, 24); buffer.writeString('ABCDE'); buffer.writeChar('F'); const cursor = buffer.getCursor(); expect(cursor.x).toBe(1); expect(cursor.y).toBe(1); const line = buffer.getLine(1); expect(line![0].char).toBe('F'); });
test('scrolls when wrapping past bottom', () => { const buffer = new ScreenBuffer(5, 3); // Fill all 3 lines for (let i = 0; i < 15; i++) { buffer.writeChar('A'); } // Write one more to trigger scroll buffer.writeChar('X'); const cursor = buffer.getCursor(); expect(cursor.y).toBe(2); // Still on last line const scrollback = buffer.getScrollback(); expect(scrollback.length).toBeGreaterThan(0); });
test('disables wrapping when autoWrap is false', () => { const buffer = new ScreenBuffer(5, 24); buffer.setAutoWrap(false); buffer.writeString('ABCDEF'); const cursor = buffer.getCursor(); expect(cursor.x).toBe(4); // Stays at last column expect(cursor.y).toBe(0); // Doesn't wrap const line = buffer.getLine(0); expect(line![4].char).toBe('F'); // Overwrites last char });});
// ============================================================================// Scrolling Tests// ============================================================================
describe('ScreenBuffer - Scrolling', () => { test('scrolls up moves lines up', () => { const buffer = new ScreenBuffer(10, 3); buffer.writeString('Line1'); buffer.moveCursorTo(0, 1); buffer.writeString('Line2'); buffer.moveCursorTo(0, 2); buffer.writeString('Line3'); buffer.scrollUp(1); const line0 = buffer.getLine(0); const line1 = buffer.getLine(1); const line2 = buffer.getLine(2); expect(line0![0].char).toBe('L'); // Was line1 expect(line1![0].char).toBe('L'); // Was line2 expect(line2![0].char).toBe(' '); // New blank line });
test('scrolling adds to scrollback', () => { const buffer = new ScreenBuffer(10, 3); buffer.writeString('TOP'); buffer.moveCursorTo(0, 1); buffer.writeString('MID'); buffer.scrollUp(1); const scrollback = buffer.getScrollback(); expect(scrollback.length).toBe(1); expect(scrollback[0][0].char).toBe('T'); });
test('scrollback respects max size', () => { const buffer = new ScreenBuffer(10, 3, 5); // Max 5 lines // Scroll 10 times for (let i = 0; i < 10; i++) { buffer.writeString('Line' + i); buffer.scrollUp(1); } const scrollback = buffer.getScrollback(); expect(scrollback.length).toBeLessThanOrEqual(5); });
test('scrolls down moves lines down', () => { const buffer = new ScreenBuffer(10, 3); buffer.writeString('Line1'); buffer.moveCursorTo(0, 1); buffer.writeString('Line2'); buffer.scrollDown(1); const line0 = buffer.getLine(0); const line1 = buffer.getLine(1); expect(line0![0].char).toBe(' '); // New blank line expect(line1![0].char).toBe('L'); // Was line0 });
test('index scrolls when at bottom', () => { const buffer = new ScreenBuffer(10, 3); buffer.moveCursorTo(0, 2); // Move to last line buffer.writeString('BOTTOM'); buffer.index(); // Should scroll const cursor = buffer.getCursor(); expect(cursor.y).toBe(2); // Still at bottom const scrollback = buffer.getScrollback(); expect(scrollback.length).toBe(1); });
test('reverseIndex scrolls when at top', () => { const buffer = new ScreenBuffer(10, 3); buffer.moveCursorTo(0, 0); // At top buffer.reverseIndex(); // Should scroll down const cursor = buffer.getCursor(); expect(cursor.y).toBe(0); // Still at top const line0 = buffer.getLine(0); expect(line0![0].char).toBe(' '); // New blank line added });});
// ============================================================================// Scroll Regions Tests// ============================================================================
describe('ScreenBuffer - Scroll Regions', () => { test('sets scroll region', () => { const buffer = new ScreenBuffer(80, 24); buffer.setScrollRegion(5, 15); const region = buffer.getScrollRegion(); expect(region).not.toBeNull(); expect(region!.top).toBe(5); expect(region!.bottom).toBe(15); });
test('scrolling respects scroll region', () => { const buffer = new ScreenBuffer(10, 10); buffer.setScrollRegion(2, 5); // Write in region buffer.moveCursorTo(0, 2); buffer.writeString('L2'); buffer.moveCursorTo(0, 3); buffer.writeString('L3'); buffer.moveCursorTo(0, 4); buffer.writeString('L4'); buffer.moveCursorTo(0, 5); buffer.writeString('L5'); buffer.scrollUp(1); // Line 2 should now have L3's content const line2 = buffer.getLine(2); expect(line2![0].char).toBe('L'); expect(line2![1].char).toBe('3'); });
test('text outside scroll region unaffected', () => { const buffer = new ScreenBuffer(10, 10); // Write outside region buffer.moveCursorTo(0, 0); buffer.writeString('OUTSIDE'); buffer.setScrollRegion(2, 5); buffer.scrollUp(2); // Line 0 should be unchanged const line0 = buffer.getLine(0); expect(line0![0].char).toBe('O'); });
test('clears scroll region', () => { const buffer = new ScreenBuffer(80, 24); buffer.setScrollRegion(5, 15); buffer.clearScrollRegion(); const region = buffer.getScrollRegion(); expect(region).toBeNull(); });
test('origin mode with scroll region', () => { const buffer = new ScreenBuffer(80, 24); buffer.setScrollRegion(5, 15); buffer.setOriginMode(true); // Cursor should be at top of region const cursor = buffer.getCursor(); expect(cursor.y).toBe(5); // Moving to 0,0 should go to region origin buffer.moveCursorTo(0, 0); expect(buffer.getCursor().y).toBe(5); });});
// ============================================================================// Erasing Tests// ============================================================================
describe('ScreenBuffer - Erasing', () => { test('eraseInLine mode 0 (cursor to end)', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HELLO'); buffer.moveCursorTo(2, 0); buffer.eraseInLine(0); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![1].char).toBe('E'); expect(line![2].char).toBe(' '); expect(line![3].char).toBe(' '); });
test('eraseInLine mode 1 (start to cursor)', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HELLO'); buffer.moveCursorTo(2, 0); buffer.eraseInLine(1); const line = buffer.getLine(0); expect(line![0].char).toBe(' '); expect(line![1].char).toBe(' '); expect(line![2].char).toBe(' '); expect(line![3].char).toBe('L'); expect(line![4].char).toBe('O'); });
test('eraseInLine mode 2 (entire line)', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HELLO'); buffer.moveCursorTo(2, 0); buffer.eraseInLine(2); const line = buffer.getLine(0); for (let i = 0; i < 5; i++) { expect(line![i].char).toBe(' '); } });
test('eraseInDisplay mode 0 (cursor to end)', () => { const buffer = new ScreenBuffer(5, 3); buffer.writeString('AAAAA'); buffer.moveCursorTo(0, 1); buffer.writeString('BBBBB'); buffer.moveCursorTo(0, 2); buffer.writeString('CCCCC'); buffer.moveCursorTo(2, 1); buffer.eraseInDisplay(0); const line0 = buffer.getLine(0); const line1 = buffer.getLine(1); const line2 = buffer.getLine(2); expect(line0![0].char).toBe('A'); // Line 0 untouched expect(line1![0].char).toBe('B'); // Start of line 1 untouched expect(line1![2].char).toBe(' '); // From cursor onward erased expect(line2![0].char).toBe(' '); // Line 2 fully erased });
test('eraseInDisplay mode 1 (start to cursor)', () => { const buffer = new ScreenBuffer(5, 3); buffer.writeString('AAAAA'); buffer.moveCursorTo(0, 1); buffer.writeString('BBBBB'); buffer.moveCursorTo(0, 2); buffer.writeString('CCCCC'); buffer.moveCursorTo(2, 1); buffer.eraseInDisplay(1); const line0 = buffer.getLine(0); const line1 = buffer.getLine(1); const line2 = buffer.getLine(2); expect(line0![0].char).toBe(' '); // Line 0 erased expect(line1![0].char).toBe(' '); // Start erased expect(line1![2].char).toBe(' '); // Up to cursor erased expect(line1![3].char).toBe('B'); // After cursor intact expect(line2![0].char).toBe('C'); // Line 2 untouched });
test('eraseInDisplay mode 2 (entire display)', () => { const buffer = new ScreenBuffer(5, 3); buffer.writeString('AAAAA'); buffer.moveCursorTo(0, 1); buffer.writeString('BBBBB'); buffer.eraseInDisplay(2); const lines = buffer.getAllLines(); for (const line of lines) { for (const cell of line) { expect(cell.char).toBe(' '); } } });
test('eraseChars erases n characters', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HELLO'); buffer.moveCursorTo(1, 0); buffer.eraseChars(3); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![1].char).toBe(' '); expect(line![2].char).toBe(' '); expect(line![3].char).toBe(' '); expect(line![4].char).toBe('O'); });});
// ============================================================================// Line/Char Operations Tests// ============================================================================
describe('ScreenBuffer - Line Operations', () => { test('insertLines adds blank lines', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeString('Line0'); buffer.moveCursorTo(0, 1); buffer.writeString('Line1'); buffer.moveCursorTo(0, 2); buffer.writeString('Line2'); buffer.moveCursorTo(0, 1); buffer.insertLines(1); const line1 = buffer.getLine(1); const line2 = buffer.getLine(2); expect(line1![0].char).toBe(' '); // New blank line expect(line2![0].char).toBe('L'); // Was Line1 });
test('deleteLines removes lines', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeString('Line0'); buffer.moveCursorTo(0, 1); buffer.writeString('Line1'); buffer.moveCursorTo(0, 2); buffer.writeString('Line2'); buffer.moveCursorTo(0, 1); buffer.deleteLines(1); const line1 = buffer.getLine(1); const line4 = buffer.getLine(4); expect(line1![0].char).toBe('L'); // Was Line2 expect(line4![0].char).toBe(' '); // New blank line at bottom });
test('insertChars shifts chars right', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HELLO'); buffer.moveCursorTo(2, 0); buffer.insertChars(2); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![1].char).toBe('E'); expect(line![2].char).toBe(' '); expect(line![3].char).toBe(' '); expect(line![4].char).toBe('L'); });
test('deleteChars shifts chars left', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HELLO'); buffer.moveCursorTo(1, 0); buffer.deleteChars(2); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![1].char).toBe('L'); expect(line![2].char).toBe('O'); expect(line![3].char).toBe(' '); });});
// ============================================================================// Modes Tests// ============================================================================
describe('ScreenBuffer - Modes', () => { test('insert mode shifts existing chars', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HI'); buffer.moveCursorTo(1, 0); buffer.setInsertMode(true); buffer.writeChar('X'); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![1].char).toBe('X'); expect(line![2].char).toBe('I'); });
test('replace mode overwrites chars', () => { const buffer = new ScreenBuffer(10, 24); buffer.writeString('HI'); buffer.moveCursorTo(1, 0); buffer.setInsertMode(false); buffer.writeChar('X'); const line = buffer.getLine(0); expect(line![0].char).toBe('H'); expect(line![1].char).toBe('X'); expect(line![2].char).toBe(' '); // I was overwritten });
test('origin mode affects cursor positioning', () => { const buffer = new ScreenBuffer(80, 24); buffer.setScrollRegion(5, 15); buffer.setOriginMode(true); buffer.moveCursorTo(10, 0); const cursor = buffer.getCursor(); // Y should be relative to scroll region top expect(cursor.y).toBe(5); });
test('cursor movement respects scroll region in origin mode', () => { const buffer = new ScreenBuffer(80, 24); buffer.setScrollRegion(5, 15); buffer.setOriginMode(true); buffer.moveCursorDown(100); // Try to go way down const cursor = buffer.getCursor(); // Should be clamped to scroll region bottom expect(cursor.y).toBe(15); });});
// ============================================================================// Resizing Tests// ============================================================================
describe('ScreenBuffer - Resizing', () => { test('resize increases width', () => { const buffer = new ScreenBuffer(10, 5); buffer.resize(15, 5); const dims = buffer.getDimensions(); expect(dims.cols).toBe(15); const line = buffer.getLine(0); expect(line!.length).toBe(15); });
test('resize decreases width', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeString('1234567890'); buffer.resize(5, 5); const dims = buffer.getDimensions(); expect(dims.cols).toBe(5); const line = buffer.getLine(0); expect(line!.length).toBe(5); });
test('resize increases height', () => { const buffer = new ScreenBuffer(10, 5); buffer.resize(10, 10); const dims = buffer.getDimensions(); expect(dims.rows).toBe(10); });
test('resize decreases height moves lines to scrollback', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeString('Line0'); buffer.moveCursorTo(0, 1); buffer.writeString('Line1'); buffer.resize(10, 1); const dims = buffer.getDimensions(); expect(dims.rows).toBe(1); const scrollback = buffer.getScrollback(); expect(scrollback.length).toBeGreaterThan(0); });
test('resize clamps cursor to new bounds', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(50, 20); buffer.resize(40, 10); const cursor = buffer.getCursor(); expect(cursor.x).toBeLessThanOrEqual(39); expect(cursor.y).toBeLessThanOrEqual(9); });});
// ============================================================================// Edge Cases Tests// ============================================================================
describe('ScreenBuffer - Edge Cases', () => { test('handles operations with n=0', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeString('TEST'); buffer.scrollUp(0); buffer.scrollDown(0); buffer.moveCursorUp(0); buffer.insertLines(0); // Should not crash or change state const line = buffer.getLine(0); expect(line![0].char).toBe('T'); });
test('handles cursor at last column without wrap', () => { const buffer = new ScreenBuffer(5, 5); buffer.setAutoWrap(false); buffer.writeString('12345'); buffer.writeChar('X'); const cursor = buffer.getCursor(); expect(cursor.x).toBe(4); expect(cursor.y).toBe(0); const line = buffer.getLine(0); expect(line![4].char).toBe('X'); });
test('handles writing on last row with scroll', () => { const buffer = new ScreenBuffer(5, 3); // Fill first 2 rows completely for (let i = 0; i < 10; i++) { buffer.writeChar('A'); } // Now on row 2 (last row). Fill it and write one more to trigger scroll for (let i = 0; i < 6; i++) { buffer.writeChar('B'); } const cursor = buffer.getCursor(); expect(cursor.y).toBe(2); // Should stay on last row const scrollback = buffer.getScrollback(); expect(scrollback.length).toBeGreaterThan(0); });
test('handles empty string write', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeChar(''); const line = buffer.getLine(0); expect(line![0].char).toBe(' '); // Should write space });
test('dirty tracking marks changed lines', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeChar('X'); expect(buffer.isDirty(0)).toBe(true); expect(buffer.isDirty(1)).toBe(false); buffer.clearDirty(); expect(buffer.isDirty(0)).toBe(false); });
test('getLine returns undefined for invalid row', () => { const buffer = new ScreenBuffer(10, 5); expect(buffer.getLine(-1)).toBeUndefined(); expect(buffer.getLine(100)).toBeUndefined(); });});
// ============================================================================// xterm.js Compatibility Tests// ============================================================================
describe('ScreenBuffer - xterm.js Compatibility', () => { test('provides baseY property', () => { const buffer = new ScreenBuffer(10, 3); // Initially no scrollback expect(buffer.baseY).toBe(0); // Scroll some lines buffer.writeString('A'.repeat(50)); // Force scrolling expect(buffer.baseY).toBeGreaterThan(0); });
test('provides cursorX and cursorY properties', () => { const buffer = new ScreenBuffer(80, 24); buffer.moveCursorTo(10, 5); expect(buffer.cursorX).toBe(10); expect(buffer.cursorY).toBe(5); });
test('provides length property', () => { const buffer = new ScreenBuffer(10, 3); // Initially just the rows expect(buffer.length).toBe(3); // After scrolling buffer.writeString('A'.repeat(50)); expect(buffer.length).toBeGreaterThan(3); });
test('provides viewportY property', () => { const buffer = new ScreenBuffer(10, 3); expect(buffer.viewportY).toBe(0); // Always 0 for Phase 1 });
test('getLine returns copy not reference', () => { const buffer = new ScreenBuffer(10, 5); buffer.writeString('TEST'); const line1 = buffer.getLine(0); const line2 = buffer.getLine(0); // Should be different objects expect(line1).not.toBe(line2); // But same content expect(line1![0].char).toBe(line2![0].char); });});