From 477075c15a949eccb54013fe846d88388b1894ec Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 17 Nov 2025 21:32:25 +0000 Subject: [PATCH 1/2] chore: fix tests --- bun.lock | 10 +-- bunfig.toml | 7 +++ happydom.ts | 96 +++++++++++++++++++++++++++++ lib/buffer.test.ts | 88 ++++++++++++++------------- lib/buffer.ts | 15 ++++- lib/scrolling.test.ts | 138 +++++++++++++++++++++++++++--------------- lib/terminal.test.ts | 93 ++++++++++++++-------------- lib/terminal.ts | 20 +++++- package.json | 2 +- 9 files changed, 322 insertions(+), 147 deletions(-) create mode 100644 bunfig.toml create mode 100644 happydom.ts diff --git a/bun.lock b/bun.lock index a4023c7..c0aa35b 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,8 @@ "name": "@cmux/ghostty-terminal", "devDependencies": { "@biomejs/biome": "^1.9.4", + "@happy-dom/global-registrator": "^15.11.0", "@types/bun": "^1.3.2", - "happy-dom": "^20.0.10", "prettier": "^3.6.2", "typescript": "^5.9.3", "vite": "^4.5.0", @@ -85,6 +85,8 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@15.11.7", "", { "dependencies": { "happy-dom": "^15.11.7" } }, "sha512-mfOoUlIw8VBiJYPrl5RZfMzkXC/z7gbSpi2ecycrj/gRWLq2CMV+Q+0G+JPjeOmuNFgg0skEIzkVFzVYFP6URw=="], + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], @@ -121,8 +123,6 @@ "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], - "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], - "@volar/language-core": ["@volar/language-core@2.4.23", "", { "dependencies": { "@volar/source-map": "2.4.23" } }, "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ=="], "@volar/source-map": ["@volar/source-map@2.4.23", "", {}, "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q=="], @@ -187,7 +187,7 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "happy-dom": ["happy-dom@20.0.10", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g=="], + "happy-dom": ["happy-dom@15.11.7", "", { "dependencies": { "entities": "^4.5.0", "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -283,6 +283,8 @@ "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..06d5b14 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,7 @@ +# Bun Configuration +# https://bun.sh/docs/runtime/bunfig + +[test] +# Preload Happy DOM before running tests to provide browser-like globals +# (window, document, HTMLElement, etc.) +preload = "./happydom.ts" diff --git a/happydom.ts b/happydom.ts new file mode 100644 index 0000000..c2b92ee --- /dev/null +++ b/happydom.ts @@ -0,0 +1,96 @@ +/** + * Happy DOM Setup for Tests + * + * This file is preloaded by Bun before running tests (configured in bunfig.toml). + * It registers Happy DOM's global objects (window, document, HTMLElement, etc.) + * so that tests requiring DOM APIs can run successfully. + * + * @see bunfig.toml - test.preload configuration + * @see https://bun.sh/docs/test/dom + */ + +import { GlobalRegistrator } from '@happy-dom/global-registrator'; + +// Register Happy DOM globals (window, document, etc.) +GlobalRegistrator.register(); + +// Mock Canvas 2D Context +// Happy DOM doesn't provide canvas rendering APIs, so we mock them for testing. +// This provides enough functionality for terminal tests to run without actual rendering. +const originalGetContext = HTMLCanvasElement.prototype.getContext; +HTMLCanvasElement.prototype.getContext = function (contextType: string, options?: any) { + if (contextType === '2d') { + // Return a minimal mock of CanvasRenderingContext2D + return { + canvas: this, + fillStyle: '#000000', + strokeStyle: '#000000', + font: '12px monospace', + textAlign: 'start', + textBaseline: 'alphabetic', + globalAlpha: 1, + globalCompositeOperation: 'source-over', + imageSmoothingEnabled: true, + lineWidth: 1, + lineCap: 'butt', + lineJoin: 'miter', + miterLimit: 10, + shadowBlur: 0, + shadowColor: 'rgba(0, 0, 0, 0)', + shadowOffsetX: 0, + shadowOffsetY: 0, + + // Drawing methods (no-ops for tests) + fillRect: () => {}, + strokeRect: () => {}, + clearRect: () => {}, + fillText: () => {}, + strokeText: () => {}, + measureText: (text: string) => ({ width: text.length * 8 }), + drawImage: () => {}, + save: () => {}, + restore: () => {}, + scale: () => {}, + rotate: () => {}, + translate: () => {}, + transform: () => {}, + setTransform: () => {}, + resetTransform: () => {}, + createLinearGradient: () => ({ + addColorStop: () => {}, + }), + createRadialGradient: () => ({ + addColorStop: () => {}, + }), + createPattern: () => null, + beginPath: () => {}, + closePath: () => {}, + moveTo: () => {}, + lineTo: () => {}, + bezierCurveTo: () => {}, + quadraticCurveTo: () => {}, + arc: () => {}, + arcTo: () => {}, + ellipse: () => {}, + rect: () => {}, + fill: () => {}, + stroke: () => {}, + clip: () => {}, + isPointInPath: () => false, + isPointInStroke: () => false, + getTransform: () => ({}), + getImageData: () => ({ + data: new Uint8ClampedArray(0), + width: 0, + height: 0, + }), + putImageData: () => {}, + createImageData: () => ({ + data: new Uint8ClampedArray(0), + width: 0, + height: 0, + }), + } as any; + } + return originalGetContext.call(this, contextType, options); +}; diff --git a/lib/buffer.test.ts b/lib/buffer.test.ts index b2bd5d6..b601896 100644 --- a/lib/buffer.test.ts +++ b/lib/buffer.test.ts @@ -33,25 +33,25 @@ describe('Buffer API', () => { describe('BufferNamespace', () => { test('should have buffer property', () => { - if (!term) return; // Skip if no DOM - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); + if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer).toBeDefined(); }); test('should have active, normal, and alternate buffers', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer.active).toBeDefined(); expect(term.buffer.normal).toBeDefined(); expect(term.buffer.alternate).toBeDefined(); }); test('active buffer should be normal by default', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer.active.type).toBe('normal'); }); test('should switch to alternate buffer', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); // Enter alternate screen (smcup) term.write('\x1b[?1049h'); @@ -60,7 +60,7 @@ describe('Buffer API', () => { }); test('should switch back to normal buffer', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); // Enter alternate screen term.write('\x1b[?1049h'); expect(term.buffer.active.type).toBe('alternate'); @@ -73,13 +73,13 @@ describe('Buffer API', () => { describe('Buffer', () => { test('should have correct type', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer.normal.type).toBe('normal'); expect(term.buffer.alternate.type).toBe('alternate'); }); test('should track cursor position', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; @@ -88,7 +88,7 @@ describe('Buffer API', () => { }); test('should track cursor position after newline', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello\r\nWorld'); const buffer = term.buffer.active; @@ -97,13 +97,13 @@ describe('Buffer API', () => { }); test('should have correct length', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.normal; expect(buffer.length).toBeGreaterThanOrEqual(24); }); test('should return null cell', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.active; const nullCell = buffer.getNullCell(); @@ -115,7 +115,7 @@ describe('Buffer API', () => { describe('BufferLine', () => { test('should get line from buffer', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -125,7 +125,7 @@ describe('Buffer API', () => { }); test('should return undefined for out of bounds line', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.active; const line = buffer.getLine(10000); @@ -133,7 +133,7 @@ describe('Buffer API', () => { }); test('should have correct isWrapped flag', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); // Write a short line (should not wrap) term.write('Short line'); const buffer = term.buffer.active; @@ -144,7 +144,7 @@ describe('Buffer API', () => { }); test('translateToString should return line content', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -154,7 +154,7 @@ describe('Buffer API', () => { }); test('translateToString should trim right when requested', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -165,7 +165,7 @@ describe('Buffer API', () => { }); test('translateToString should respect startColumn and endColumn', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -177,7 +177,7 @@ describe('Buffer API', () => { describe('BufferCell', () => { test('should get cell from line', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -187,7 +187,7 @@ describe('Buffer API', () => { }); test('should return undefined for out of bounds cell', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -197,7 +197,7 @@ describe('Buffer API', () => { }); test('getChars should return character', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('H'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -207,7 +207,7 @@ describe('Buffer API', () => { }); test('getCode should return codepoint', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('A'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -217,7 +217,7 @@ describe('Buffer API', () => { }); test('getWidth should return 1 for normal characters', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('A'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -227,7 +227,7 @@ describe('Buffer API', () => { }); test('should detect bold text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[1mBold\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -237,7 +237,7 @@ describe('Buffer API', () => { }); test('should detect italic text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[3mItalic\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -247,7 +247,7 @@ describe('Buffer API', () => { }); test('should detect underline text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[4mUnderline\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -257,7 +257,7 @@ describe('Buffer API', () => { }); test('should detect strikethrough text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[9mStrike\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -267,7 +267,7 @@ describe('Buffer API', () => { }); test('should detect blink text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[5mBlink\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -277,7 +277,7 @@ describe('Buffer API', () => { }); test('should detect inverse text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[7mInverse\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -287,7 +287,7 @@ describe('Buffer API', () => { }); test('should detect invisible text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[8mInvisible\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -297,7 +297,7 @@ describe('Buffer API', () => { }); test('should detect faint text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[2mFaint\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -307,7 +307,7 @@ describe('Buffer API', () => { }); test('should return RGB foreground color', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[31mRed\x1b[0m'); // ANSI red const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -318,7 +318,7 @@ describe('Buffer API', () => { }); test('should return RGB background color', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[41mRed BG\x1b[0m'); // ANSI red background const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -329,7 +329,7 @@ describe('Buffer API', () => { }); test('empty cell should return empty string', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); // Get a cell that was never written to const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -342,7 +342,7 @@ describe('Buffer API', () => { describe('Multi-line content', () => { test('should handle multiple lines correctly', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Line 1\r\n'); term.write('Line 2\r\n'); term.write('Line 3'); @@ -359,7 +359,7 @@ describe('Buffer API', () => { }); test('should handle colored multi-line content', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); 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'); @@ -383,7 +383,7 @@ describe('Buffer API', () => { describe('Unicode support', () => { test('should handle emoji correctly', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('😀'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -394,7 +394,7 @@ describe('Buffer API', () => { }); test('should handle accented characters', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Héllo'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -403,7 +403,7 @@ describe('Buffer API', () => { }); test('should handle various Unicode characters', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('日本語'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -414,7 +414,7 @@ describe('Buffer API', () => { describe('Edge cases', () => { test('should handle empty buffer', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -423,7 +423,7 @@ describe('Buffer API', () => { }); test('should handle full line of text', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); // Write exactly 80 characters const fullLine = 'A'.repeat(80); term.write(fullLine); @@ -435,15 +435,17 @@ describe('Buffer API', () => { }); test('should handle cursor at end of line', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('X'.repeat(80)); const buffer = term.buffer.active; - expect(buffer.cursorX).toBe(80); + // 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', () => { - if (!term) return; // Skip if no DOM + if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[1;3;4mBold+Italic+Underline\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); diff --git a/lib/buffer.ts b/lib/buffer.ts index 05f7517..1a6fd83 100644 --- a/lib/buffer.ts +++ b/lib/buffer.ts @@ -300,10 +300,21 @@ export class BufferCell implements IBufferCell { } getChars(): string { - if (this.cell.codepoint === 0) { + const codepoint = this.cell.codepoint; + + // Return empty string for null character or invalid codepoints + if (codepoint === 0) { return ''; } - return String.fromCodePoint(this.cell.codepoint); + + // Validate codepoint is within valid Unicode range + // Valid: 0x0000 to 0x10FFFF, excluding surrogates (0xD800-0xDFFF) + if (codepoint < 0 || codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff)) { + // Return replacement character for invalid codepoints + return '\uFFFD'; + } + + return String.fromCodePoint(codepoint); } getCode(): number { diff --git a/lib/scrolling.test.ts b/lib/scrolling.test.ts index 5d54815..d7eddaa 100644 --- a/lib/scrolling.test.ts +++ b/lib/scrolling.test.ts @@ -6,7 +6,8 @@ describe('Terminal Scrolling', () => { let container: HTMLElement; beforeEach(async () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); terminal = new Terminal({ cols: 80, rows: 24 }); @@ -24,7 +25,8 @@ describe('Terminal Scrolling', () => { describe('Normal Screen Mode', () => { test('should scroll viewport on wheel event in normal mode', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); // Fill with enough lines to create scrollback for (let i = 0; i < 50; i++) { @@ -42,12 +44,13 @@ describe('Terminal Scrolling', () => { }); container.dispatchEvent(wheelEvent); - // Viewport should have scrolled up - expect(terminal.viewportY).toBeLessThan(initialViewportY); + // Viewport should have scrolled up (viewportY increases away from 0) + expect(terminal.viewportY).toBeGreaterThan(initialViewportY); }); test('should scroll down on positive deltaY', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); // Fill with scrollback for (let i = 0; i < 50; i++) { @@ -66,12 +69,13 @@ describe('Terminal Scrolling', () => { }); container.dispatchEvent(wheelEvent); - // Viewport should have scrolled down - expect(terminal.viewportY).toBeGreaterThan(scrolledUpViewportY); + // Viewport should have scrolled down (viewportY decreases towards 0) + expect(terminal.viewportY).toBeLessThan(scrolledUpViewportY); }); test('should not send data to application in normal mode', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -91,19 +95,22 @@ describe('Terminal Scrolling', () => { describe('Alternate Screen Mode', () => { beforeEach(() => { - if (typeof document === 'undefined' || !terminal) return; // Skip if no DOM + if (typeof document === 'undefined' || !terminal) + throw new Error('DOM environment not available - check happydom setup'); // Enter alternate screen mode (vim, less, htop, etc.) terminal.write('\x1B[?1049h'); }); test('should detect alternate screen mode', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); expect(terminal.wasmTerm?.isAlternateScreen()).toBe(true); }); test('should send arrow up sequences on wheel up in alternate screen', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -123,7 +130,8 @@ describe('Terminal Scrolling', () => { }); test('should send arrow down sequences on wheel down in alternate screen', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -143,7 +151,8 @@ describe('Terminal Scrolling', () => { }); test('should not scroll viewport in alternate screen', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const initialViewportY = terminal.viewportY; @@ -160,7 +169,8 @@ describe('Terminal Scrolling', () => { }); test('should cap arrow count at 5 per wheel event', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -180,7 +190,8 @@ describe('Terminal Scrolling', () => { describe('Mode Transitions', () => { test('should switch behavior when entering alternate screen', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); // Start in normal mode for (let i = 0; i < 30; i++) { @@ -219,7 +230,8 @@ describe('Terminal Scrolling', () => { }); test('should switch back to viewport scrolling when exiting alternate screen', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); // Enter alternate screen terminal.write('\x1B[?1049h'); @@ -247,15 +259,16 @@ describe('Terminal Scrolling', () => { }); container.dispatchEvent(wheelEvent); - // Should have scrolled, not sent data + // Should have scrolled up, not sent data expect(dataSent.length).toBe(0); - expect(terminal.viewportY).toBeLessThan(initialViewportY); + expect(terminal.viewportY).toBeGreaterThan(initialViewportY); }); }); describe('Custom Wheel Handler', () => { test('should respect custom wheel handler in both modes', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); let customHandlerCalled = false; terminal.attachCustomWheelEventHandler(() => { @@ -274,7 +287,8 @@ describe('Terminal Scrolling', () => { }); test('custom handler can delegate to default behavior', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); terminal.attachCustomWheelEventHandler(() => { return false; // Don't override, use default @@ -301,7 +315,8 @@ describe('Terminal Scrolling', () => { describe('Edge Cases', () => { test('should handle zero deltaY gracefully', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -318,7 +333,8 @@ describe('Terminal Scrolling', () => { }); test('should handle very small deltaY values', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -338,7 +354,8 @@ describe('Terminal Scrolling', () => { }); test('should handle terminal not yet opened', () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); const closedTerminal = new Terminal({ cols: 80, rows: 24 }); @@ -366,7 +383,8 @@ describe('Scrolling Methods', () => { let container: HTMLDivElement | null = null; beforeEach(async () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); term = new Terminal({ cols: 80, rows: 24, scrollback: 1000 }); @@ -374,7 +392,8 @@ describe('Scrolling Methods', () => { }); afterEach(() => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); term.dispose(); document.body.removeChild(container); term = null; @@ -382,7 +401,8 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should scroll viewport up', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write some content to create scrollback for (let i = 0; i < 50; i++) { @@ -397,7 +417,8 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should scroll viewport down', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content and scroll up first for (let i = 0; i < 50; i++) { @@ -413,7 +434,8 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should not scroll beyond bounds', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write limited content for (let i = 0; i < 10; i++) { @@ -429,7 +451,8 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should not scroll below bottom', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content and scroll up for (let i = 0; i < 50; i++) { @@ -445,7 +468,8 @@ describe('Scrolling Methods', () => { }); test('scrollPages() should scroll by page', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content for (let i = 0; i < 100; i++) { @@ -460,7 +484,8 @@ describe('Scrolling Methods', () => { }); test('scrollToTop() should scroll to top of buffer', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content for (let i = 0; i < 50; i++) { @@ -476,7 +501,8 @@ describe('Scrolling Methods', () => { }); test('scrollToBottom() should scroll to bottom', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content and scroll up for (let i = 0; i < 50; i++) { @@ -492,7 +518,8 @@ describe('Scrolling Methods', () => { }); test('scrollToLine() should scroll to specific line', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content for (let i = 0; i < 50; i++) { @@ -506,7 +533,8 @@ describe('Scrolling Methods', () => { }); test('scrollToLine() should clamp to valid range', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write limited content for (let i = 0; i < 10; i++) { @@ -522,7 +550,8 @@ describe('Scrolling Methods', () => { }); test('scrollToLine() should handle negative values', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content for (let i = 0; i < 50; i++) { @@ -542,7 +571,8 @@ describe('Scroll Events', () => { let container: HTMLDivElement | null = null; beforeEach(async () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); term = new Terminal({ cols: 80, rows: 24, scrollback: 1000 }); @@ -550,7 +580,8 @@ describe('Scroll Events', () => { }); afterEach(() => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); term.dispose(); document.body.removeChild(container); term = null; @@ -558,7 +589,8 @@ describe('Scroll Events', () => { }); test('onScroll should fire when scrolling', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); let scrollPosition = -1; let fireCount = 0; @@ -581,7 +613,8 @@ describe('Scroll Events', () => { }); test('onScroll should not fire if position unchanged', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); let fireCount = 0; @@ -596,7 +629,8 @@ describe('Scroll Events', () => { }); test('onScroll should fire multiple times for multiple scrolls', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); const positions: number[] = []; @@ -624,7 +658,8 @@ describe('Scroll Events', () => { // implementation. Firing it every frame causes performance issues. test('onCursorMove should fire when cursor moves', async () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); let moveCount = 0; @@ -649,7 +684,8 @@ describe('Custom Wheel Event Handler', () => { let container: HTMLDivElement | null = null; beforeEach(async () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); term = new Terminal({ cols: 80, rows: 24, scrollback: 1000 }); @@ -657,7 +693,8 @@ describe('Custom Wheel Event Handler', () => { }); afterEach(() => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); term.dispose(); document.body.removeChild(container); term = null; @@ -665,7 +702,8 @@ describe('Custom Wheel Event Handler', () => { }); test('attachCustomWheelEventHandler() should set handler', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); const handler = () => true; term.attachCustomWheelEventHandler(handler); @@ -674,7 +712,8 @@ describe('Custom Wheel Event Handler', () => { }); test('attachCustomWheelEventHandler() should allow clearing handler', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); const handler = () => true; term.attachCustomWheelEventHandler(handler); @@ -684,7 +723,8 @@ describe('Custom Wheel Event Handler', () => { }); test('custom wheel handler should block default scrolling when returning true', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); let handlerCalled = false; @@ -708,7 +748,8 @@ describe('Custom Wheel Event Handler', () => { }); test('custom wheel handler should allow default scrolling when returning false', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); let handlerCalled = false; @@ -732,7 +773,8 @@ describe('Custom Wheel Event Handler', () => { }); test('wheel events should scroll terminal by default', () => { - if (!term || !container) return; // Skip if no DOM + if (!term || !container) + throw new Error('DOM environment not available - check happydom setup'); // Write content for (let i = 0; i < 50; i++) { diff --git a/lib/terminal.test.ts b/lib/terminal.test.ts index c3824e2..7acd312 100644 --- a/lib/terminal.test.ts +++ b/lib/terminal.test.ts @@ -73,7 +73,7 @@ describe('Terminal', () => { }); test('cannot write after disposal', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -83,7 +83,7 @@ describe('Terminal', () => { }); test('cannot open twice', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -94,7 +94,7 @@ describe('Terminal', () => { }); test('cannot open after disposal', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); term.dispose(); @@ -111,7 +111,7 @@ describe('Terminal', () => { }); test('exposes element after open', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); expect(term.element).toBeUndefined(); @@ -149,7 +149,7 @@ describe('Terminal', () => { }); test('onResize fires when terminal is resized', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal({ cols: 80, rows: 24 }); await term.open(container); @@ -169,7 +169,7 @@ describe('Terminal', () => { }); test('onBell fires on bell character', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -192,7 +192,7 @@ describe('Terminal', () => { describe('Writing', () => { test('write() does not throw after open', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -203,7 +203,7 @@ describe('Terminal', () => { }); test('write() accepts string', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -214,7 +214,7 @@ describe('Terminal', () => { }); test('write() accepts Uint8Array', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -226,7 +226,7 @@ describe('Terminal', () => { }); test('writeln() adds newline', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -239,7 +239,7 @@ describe('Terminal', () => { describe('Resizing', () => { test('resize() updates dimensions', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal({ cols: 80, rows: 24 }); await term.open(container); @@ -253,7 +253,7 @@ describe('Terminal', () => { }); test('resize() with same dimensions is no-op', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal({ cols: 80, rows: 24 }); await term.open(container); @@ -276,7 +276,7 @@ describe('Terminal', () => { describe('Control Methods', () => { test('clear() does not throw', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -287,7 +287,7 @@ describe('Terminal', () => { }); test('reset() does not throw', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -298,7 +298,7 @@ describe('Terminal', () => { }); test('focus() does not throw', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -316,7 +316,7 @@ describe('Terminal', () => { describe('Addons', () => { test('loadAddon() accepts addon', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -336,7 +336,7 @@ describe('Terminal', () => { }); test('loadAddon() calls activate', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -357,7 +357,7 @@ describe('Terminal', () => { }); test('dispose() calls addon dispose', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -379,7 +379,7 @@ describe('Terminal', () => { describe('Integration', () => { test('can write ANSI sequences', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -393,7 +393,7 @@ describe('Terminal', () => { }); test('can handle cursor movement sequences', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -406,7 +406,7 @@ describe('Terminal', () => { }); test('multiple write calls work', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -423,7 +423,7 @@ describe('Terminal', () => { describe('Disposal', () => { test('dispose() can be called multiple times', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -433,7 +433,7 @@ describe('Terminal', () => { }); test('dispose() cleans up canvas element', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); const term = new Terminal(); await term.open(container); @@ -1113,14 +1113,14 @@ describe('Buffer Access API', () => { }); test('isAlternateScreen() starts false', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); await term.open(container); expect(term.wasmTerm?.isAlternateScreen()).toBe(false); }); test('isAlternateScreen() detects alternate screen mode', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); await term.open(container); @@ -1134,7 +1134,7 @@ describe('Buffer Access API', () => { }); test('isRowWrapped() returns false for normal line breaks', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); await term.open(container); term.write('Line 1\r\nLine 2\r\n'); @@ -1144,7 +1144,8 @@ describe('Buffer Access API', () => { }); test('isRowWrapped() detects wrapped lines', async () => { - if (typeof document === 'undefined') return; // Skip if no DOM + if (typeof document === 'undefined') + throw new Error('DOM environment not available - check happydom setup'); // Create narrow terminal to force wrapping const narrowTerm = new Terminal({ cols: 20, rows: 10 }); @@ -1166,7 +1167,7 @@ describe('Buffer Access API', () => { }); test('isRowWrapped() handles edge cases', async () => { - if (!container) return; // Skip if no DOM + if (!container) throw new Error('DOM environment not available - check happydom setup'); await term.open(container); @@ -1336,23 +1337,23 @@ describe('Selection with Scrollback', () => { term.write(`Line ${lineNum}: This is line number ${i}\r\n`); } - // At this point, the screen buffer shows lines 76-99 (last 24 lines) - // The scrollback buffer contains lines 0-75 + // At this point, the screen buffer shows lines 77-99 (last 23 lines) + // The scrollback buffer contains lines 0-76 (77 lines total) // Scroll up 50 lines to view older content term.scrollLines(-50); expect(term.viewportY).toBe(50); // The viewport now shows: - // - Lines 0-23 of viewport = Lines 26-49 of the original output - // (because scrollback length is 76, viewportY is 50) - // Viewport line 0 = scrollback offset (76 - 50 + 0) = 26 + // - Lines 0-23 of viewport = Lines 27-50 of the original output + // (because scrollback length is 77, viewportY is 50) + // Viewport line 0 = scrollback offset (77 - 50 + 0) = 27 // Select from viewport row 5, col 0 to viewport row 7, col 20 // This should select: - // - Viewport row 5 = Line 031 (scrollback offset 76-50+5 = 31) - // - Viewport row 6 = Line 032 - // - Viewport row 7 = Line 033 (first 20 chars) + // - Viewport row 5 = Line 032 (scrollback offset 77-50+5 = 32) + // - Viewport row 6 = Line 033 + // - Viewport row 7 = Line 034 (first 20 chars) // Use the internal selection manager to set selection if ((term as any).selectionManager) { @@ -1362,10 +1363,10 @@ describe('Selection with Scrollback', () => { const selectedText = selMgr.getSelection(); - // Should contain "Line 031", "Line 032", and start of "Line 033" - expect(selectedText).toContain('Line 031'); + // Should contain "Line 032", "Line 033", and start of "Line 034" expect(selectedText).toContain('Line 032'); expect(selectedText).toContain('Line 033'); + expect(selectedText).toContain('Line 034'); // Should NOT contain current screen buffer content (lines 76-99) expect(selectedText).not.toContain('Line 076'); @@ -1392,8 +1393,8 @@ describe('Selection with Scrollback', () => { expect(term.viewportY).toBe(10); // Now viewport shows: - // - Top 10 rows: scrollback content (lines 66-75) - // - Bottom 14 rows: screen buffer content (lines 76-89, 90-99 are below viewport) + // - Top 10 rows: scrollback content (lines 67-76) + // - Bottom 14 rows: screen buffer content (lines 77-90) // Select from row 8 (in scrollback) to row 12 (in screen buffer) if ((term as any).selectionManager) { @@ -1403,14 +1404,14 @@ describe('Selection with Scrollback', () => { const selectedText = selMgr.getSelection(); - // Row 8 is in scrollback (scrollback offset: 76-10+8 = 74) - // Rows 9 is in scrollback (offset 75) - // Rows 10-12 are in screen (screen rows 0-2, which are lines 76-78) - expect(selectedText).toContain('Line 074'); + // Row 8 is in scrollback (scrollback offset: 77-10+8 = 75) + // Row 9 is in scrollback (offset 76) + // Rows 10-12 are in screen (screen rows 0-2, which are lines 77-79) expect(selectedText).toContain('Line 075'); expect(selectedText).toContain('Line 076'); expect(selectedText).toContain('Line 077'); expect(selectedText).toContain('Line 078'); + expect(selectedText).toContain('Line 079'); } term.dispose(); @@ -1438,10 +1439,10 @@ describe('Selection with Scrollback', () => { const selectedText = selMgr.getSelection(); - // Should get lines from screen buffer (lines 76-99 visible, we select first 3) - expect(selectedText).toContain('Line 076'); + // Should get lines from screen buffer (lines 77-99 visible, we select first 3) expect(selectedText).toContain('Line 077'); expect(selectedText).toContain('Line 078'); + expect(selectedText).toContain('Line 079'); } term.dispose(); diff --git a/lib/terminal.ts b/lib/terminal.ts index 110f7d5..54af8ff 100644 --- a/lib/terminal.ts +++ b/lib/terminal.ts @@ -324,6 +324,14 @@ export class Terminal implements ITerminalCore { // Write directly to WASM terminal (handles VT parsing internally) this.wasmTerm!.write(data); + // Check for bell character (BEL, \x07) + // WASM doesn't expose bell events, so we detect it in the data stream + if (typeof data === 'string' && data.includes('\x07')) { + this.bellEmitter.fire(); + } else if (data instanceof Uint8Array && data.includes(0x07)) { + this.bellEmitter.fire(); + } + // Invalidate link cache (content changed) this.linkDetector?.invalidateCache(); @@ -821,6 +829,12 @@ export class Terminal implements ITerminalCore { this.canvas = undefined; } + // Remove textarea from DOM + if (this.textarea && this.textarea.parentNode) { + this.textarea.parentNode.removeChild(this.textarea); + this.textarea = undefined; + } + // Remove event listeners if (this.element) { this.element.removeEventListener('wheel', this.handleWheel); @@ -863,12 +877,12 @@ export class Terminal implements ITerminalCore { * Assert terminal is open (throw if not) */ private assertOpen(): void { - if (!this.isOpen) { - throw new Error('Terminal must be opened before use. Call terminal.open(parent) first.'); - } if (this.isDisposed) { throw new Error('Terminal has been disposed'); } + if (!this.isOpen) { + throw new Error('Terminal must be opened before use. Call terminal.open(parent) first.'); + } } /** diff --git a/package.json b/package.json index 17f4e07..0a686d8 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,8 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@happy-dom/global-registrator": "^15.11.0", "@types/bun": "^1.3.2", - "happy-dom": "^20.0.10", "prettier": "^3.6.2", "typescript": "^5.9.3", "vite": "^4.5.0", From edc2f22a127bf693724817f892e683fb9a3eeb9f Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 17 Nov 2025 21:38:37 +0000 Subject: [PATCH 2/2] rm checks --- lib/buffer.test.ts | 42 --------------- lib/scrolling.test.ts | 118 ------------------------------------------ lib/terminal.test.ts | 46 ---------------- 3 files changed, 206 deletions(-) diff --git a/lib/buffer.test.ts b/lib/buffer.test.ts index b601896..7244867 100644 --- a/lib/buffer.test.ts +++ b/lib/buffer.test.ts @@ -33,25 +33,20 @@ describe('Buffer API', () => { describe('BufferNamespace', () => { test('should have buffer property', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); - if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer).toBeDefined(); }); test('should have active, normal, and alternate buffers', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer.active).toBeDefined(); expect(term.buffer.normal).toBeDefined(); expect(term.buffer.alternate).toBeDefined(); }); test('active buffer should be normal by default', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer.active.type).toBe('normal'); }); test('should switch to alternate buffer', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); // Enter alternate screen (smcup) term.write('\x1b[?1049h'); @@ -60,7 +55,6 @@ describe('Buffer API', () => { }); test('should switch back to normal buffer', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); // Enter alternate screen term.write('\x1b[?1049h'); expect(term.buffer.active.type).toBe('alternate'); @@ -73,13 +67,11 @@ describe('Buffer API', () => { describe('Buffer', () => { test('should have correct type', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); expect(term.buffer.normal.type).toBe('normal'); expect(term.buffer.alternate.type).toBe('alternate'); }); test('should track cursor position', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; @@ -88,7 +80,6 @@ describe('Buffer API', () => { }); test('should track cursor position after newline', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello\r\nWorld'); const buffer = term.buffer.active; @@ -97,13 +88,11 @@ describe('Buffer API', () => { }); test('should have correct length', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.normal; expect(buffer.length).toBeGreaterThanOrEqual(24); }); test('should return null cell', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.active; const nullCell = buffer.getNullCell(); @@ -115,7 +104,6 @@ describe('Buffer API', () => { describe('BufferLine', () => { test('should get line from buffer', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -125,7 +113,6 @@ describe('Buffer API', () => { }); test('should return undefined for out of bounds line', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.active; const line = buffer.getLine(10000); @@ -133,7 +120,6 @@ describe('Buffer API', () => { }); test('should have correct isWrapped flag', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); // Write a short line (should not wrap) term.write('Short line'); const buffer = term.buffer.active; @@ -144,7 +130,6 @@ describe('Buffer API', () => { }); test('translateToString should return line content', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -154,7 +139,6 @@ describe('Buffer API', () => { }); test('translateToString should trim right when requested', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -165,7 +149,6 @@ describe('Buffer API', () => { }); test('translateToString should respect startColumn and endColumn', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello, World!'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -177,7 +160,6 @@ describe('Buffer API', () => { describe('BufferCell', () => { test('should get cell from line', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -187,7 +169,6 @@ describe('Buffer API', () => { }); test('should return undefined for out of bounds cell', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Hello'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -197,7 +178,6 @@ describe('Buffer API', () => { }); test('getChars should return character', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('H'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -207,7 +187,6 @@ describe('Buffer API', () => { }); test('getCode should return codepoint', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('A'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -217,7 +196,6 @@ describe('Buffer API', () => { }); test('getWidth should return 1 for normal characters', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('A'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -227,7 +205,6 @@ describe('Buffer API', () => { }); test('should detect bold text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[1mBold\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -237,7 +214,6 @@ describe('Buffer API', () => { }); test('should detect italic text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[3mItalic\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -247,7 +223,6 @@ describe('Buffer API', () => { }); test('should detect underline text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[4mUnderline\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -257,7 +232,6 @@ describe('Buffer API', () => { }); test('should detect strikethrough text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[9mStrike\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -267,7 +241,6 @@ describe('Buffer API', () => { }); test('should detect blink text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[5mBlink\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -277,7 +250,6 @@ describe('Buffer API', () => { }); test('should detect inverse text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[7mInverse\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -287,7 +259,6 @@ describe('Buffer API', () => { }); test('should detect invisible text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[8mInvisible\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -297,7 +268,6 @@ describe('Buffer API', () => { }); test('should detect faint text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[2mFaint\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -307,7 +277,6 @@ describe('Buffer API', () => { }); test('should return RGB foreground color', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[31mRed\x1b[0m'); // ANSI red const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -318,7 +287,6 @@ describe('Buffer API', () => { }); test('should return RGB background color', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[41mRed BG\x1b[0m'); // ANSI red background const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -329,7 +297,6 @@ describe('Buffer API', () => { }); test('empty cell should return empty string', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); // Get a cell that was never written to const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -342,7 +309,6 @@ describe('Buffer API', () => { describe('Multi-line content', () => { test('should handle multiple lines correctly', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Line 1\r\n'); term.write('Line 2\r\n'); term.write('Line 3'); @@ -359,7 +325,6 @@ describe('Buffer API', () => { }); test('should handle colored multi-line content', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); 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'); @@ -383,7 +348,6 @@ describe('Buffer API', () => { describe('Unicode support', () => { test('should handle emoji correctly', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('😀'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -394,7 +358,6 @@ describe('Buffer API', () => { }); test('should handle accented characters', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('Héllo'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -403,7 +366,6 @@ describe('Buffer API', () => { }); test('should handle various Unicode characters', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('日本語'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -414,7 +376,6 @@ describe('Buffer API', () => { describe('Edge cases', () => { test('should handle empty buffer', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); const buffer = term.buffer.active; const line = buffer.getLine(0); @@ -423,7 +384,6 @@ describe('Buffer API', () => { }); test('should handle full line of text', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); // Write exactly 80 characters const fullLine = 'A'.repeat(80); term.write(fullLine); @@ -435,7 +395,6 @@ describe('Buffer API', () => { }); test('should handle cursor at end of line', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('X'.repeat(80)); const buffer = term.buffer.active; @@ -445,7 +404,6 @@ describe('Buffer API', () => { }); test('should handle multiple style attributes', () => { - if (!term) throw new Error('DOM environment not available - check happydom setup'); term.write('\x1b[1;3;4mBold+Italic+Underline\x1b[0m'); const buffer = term.buffer.active; const line = buffer.getLine(0); diff --git a/lib/scrolling.test.ts b/lib/scrolling.test.ts index d7eddaa..106d4b0 100644 --- a/lib/scrolling.test.ts +++ b/lib/scrolling.test.ts @@ -6,8 +6,6 @@ describe('Terminal Scrolling', () => { let container: HTMLElement; beforeEach(async () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); terminal = new Terminal({ cols: 80, rows: 24 }); @@ -25,9 +23,6 @@ describe('Terminal Scrolling', () => { describe('Normal Screen Mode', () => { test('should scroll viewport on wheel event in normal mode', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - // Fill with enough lines to create scrollback for (let i = 0; i < 50; i++) { terminal.write(`Line ${i}\r\n`); @@ -49,9 +44,6 @@ describe('Terminal Scrolling', () => { }); test('should scroll down on positive deltaY', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - // Fill with scrollback for (let i = 0; i < 50; i++) { terminal.write(`Line ${i}\r\n`); @@ -74,9 +66,6 @@ describe('Terminal Scrolling', () => { }); test('should not send data to application in normal mode', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -95,23 +84,15 @@ describe('Terminal Scrolling', () => { describe('Alternate Screen Mode', () => { beforeEach(() => { - if (typeof document === 'undefined' || !terminal) - throw new Error('DOM environment not available - check happydom setup'); // Enter alternate screen mode (vim, less, htop, etc.) terminal.write('\x1B[?1049h'); }); test('should detect alternate screen mode', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - expect(terminal.wasmTerm?.isAlternateScreen()).toBe(true); }); test('should send arrow up sequences on wheel up in alternate screen', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -130,9 +111,6 @@ describe('Terminal Scrolling', () => { }); test('should send arrow down sequences on wheel down in alternate screen', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -151,9 +129,6 @@ describe('Terminal Scrolling', () => { }); test('should not scroll viewport in alternate screen', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const initialViewportY = terminal.viewportY; // Simulate wheel event @@ -169,9 +144,6 @@ describe('Terminal Scrolling', () => { }); test('should cap arrow count at 5 per wheel event', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -190,9 +162,6 @@ describe('Terminal Scrolling', () => { describe('Mode Transitions', () => { test('should switch behavior when entering alternate screen', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - // Start in normal mode for (let i = 0; i < 30; i++) { terminal.write(`Line ${i}\r\n`); @@ -230,9 +199,6 @@ describe('Terminal Scrolling', () => { }); test('should switch back to viewport scrolling when exiting alternate screen', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - // Enter alternate screen terminal.write('\x1B[?1049h'); expect(terminal.wasmTerm?.isAlternateScreen()).toBe(true); @@ -267,9 +233,6 @@ describe('Terminal Scrolling', () => { describe('Custom Wheel Handler', () => { test('should respect custom wheel handler in both modes', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - let customHandlerCalled = false; terminal.attachCustomWheelEventHandler(() => { customHandlerCalled = true; @@ -287,9 +250,6 @@ describe('Terminal Scrolling', () => { }); test('custom handler can delegate to default behavior', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - terminal.attachCustomWheelEventHandler(() => { return false; // Don't override, use default }); @@ -315,9 +275,6 @@ describe('Terminal Scrolling', () => { describe('Edge Cases', () => { test('should handle zero deltaY gracefully', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -333,9 +290,6 @@ describe('Terminal Scrolling', () => { }); test('should handle very small deltaY values', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const dataSent: string[] = []; terminal.onData((data) => dataSent.push(data)); @@ -354,9 +308,6 @@ describe('Terminal Scrolling', () => { }); test('should handle terminal not yet opened', () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); - const closedTerminal = new Terminal({ cols: 80, rows: 24 }); // Should not crash when handleWheel is called without wasmTerm @@ -383,8 +334,6 @@ describe('Scrolling Methods', () => { let container: HTMLDivElement | null = null; beforeEach(async () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); term = new Terminal({ cols: 80, rows: 24, scrollback: 1000 }); @@ -392,8 +341,6 @@ describe('Scrolling Methods', () => { }); afterEach(() => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); term.dispose(); document.body.removeChild(container); term = null; @@ -401,9 +348,6 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should scroll viewport up', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write some content to create scrollback for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); @@ -417,9 +361,6 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should scroll viewport down', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content and scroll up first for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); @@ -434,9 +375,6 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should not scroll beyond bounds', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write limited content for (let i = 0; i < 10; i++) { term.write(`Line ${i}\r\n`); @@ -451,9 +389,6 @@ describe('Scrolling Methods', () => { }); test('scrollLines() should not scroll below bottom', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content and scroll up for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); @@ -468,9 +403,6 @@ describe('Scrolling Methods', () => { }); test('scrollPages() should scroll by page', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content for (let i = 0; i < 100; i++) { term.write(`Line ${i}\r\n`); @@ -484,9 +416,6 @@ describe('Scrolling Methods', () => { }); test('scrollToTop() should scroll to top of buffer', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); @@ -501,9 +430,6 @@ describe('Scrolling Methods', () => { }); test('scrollToBottom() should scroll to bottom', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content and scroll up for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); @@ -518,9 +444,6 @@ describe('Scrolling Methods', () => { }); test('scrollToLine() should scroll to specific line', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); @@ -533,9 +456,6 @@ describe('Scrolling Methods', () => { }); test('scrollToLine() should clamp to valid range', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write limited content for (let i = 0; i < 10; i++) { term.write(`Line ${i}\r\n`); @@ -550,9 +470,6 @@ describe('Scrolling Methods', () => { }); test('scrollToLine() should handle negative values', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); @@ -571,8 +488,6 @@ describe('Scroll Events', () => { let container: HTMLDivElement | null = null; beforeEach(async () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); term = new Terminal({ cols: 80, rows: 24, scrollback: 1000 }); @@ -580,8 +495,6 @@ describe('Scroll Events', () => { }); afterEach(() => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); term.dispose(); document.body.removeChild(container); term = null; @@ -589,9 +502,6 @@ describe('Scroll Events', () => { }); test('onScroll should fire when scrolling', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - let scrollPosition = -1; let fireCount = 0; @@ -613,9 +523,6 @@ describe('Scroll Events', () => { }); test('onScroll should not fire if position unchanged', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - let fireCount = 0; term.onScroll(() => { @@ -629,9 +536,6 @@ describe('Scroll Events', () => { }); test('onScroll should fire multiple times for multiple scrolls', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - const positions: number[] = []; term.onScroll((position) => { @@ -658,9 +562,6 @@ describe('Scroll Events', () => { // implementation. Firing it every frame causes performance issues. test('onCursorMove should fire when cursor moves', async () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - let moveCount = 0; term.onCursorMove(() => { @@ -684,8 +585,6 @@ describe('Custom Wheel Event Handler', () => { let container: HTMLDivElement | null = null; beforeEach(async () => { - if (typeof document === 'undefined') - throw new Error('DOM environment not available - check happydom setup'); container = document.createElement('div'); document.body.appendChild(container); term = new Terminal({ cols: 80, rows: 24, scrollback: 1000 }); @@ -693,8 +592,6 @@ describe('Custom Wheel Event Handler', () => { }); afterEach(() => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); term.dispose(); document.body.removeChild(container); term = null; @@ -702,9 +599,6 @@ describe('Custom Wheel Event Handler', () => { }); test('attachCustomWheelEventHandler() should set handler', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - const handler = () => true; term.attachCustomWheelEventHandler(handler); @@ -712,9 +606,6 @@ describe('Custom Wheel Event Handler', () => { }); test('attachCustomWheelEventHandler() should allow clearing handler', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - const handler = () => true; term.attachCustomWheelEventHandler(handler); term.attachCustomWheelEventHandler(undefined); @@ -723,9 +614,6 @@ describe('Custom Wheel Event Handler', () => { }); test('custom wheel handler should block default scrolling when returning true', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - let handlerCalled = false; term.attachCustomWheelEventHandler(() => { @@ -748,9 +636,6 @@ describe('Custom Wheel Event Handler', () => { }); test('custom wheel handler should allow default scrolling when returning false', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - let handlerCalled = false; term.attachCustomWheelEventHandler(() => { @@ -773,9 +658,6 @@ describe('Custom Wheel Event Handler', () => { }); test('wheel events should scroll terminal by default', () => { - if (!term || !container) - throw new Error('DOM environment not available - check happydom setup'); - // Write content for (let i = 0; i < 50; i++) { term.write(`Line ${i}\r\n`); diff --git a/lib/terminal.test.ts b/lib/terminal.test.ts index 7acd312..a465ae3 100644 --- a/lib/terminal.test.ts +++ b/lib/terminal.test.ts @@ -73,8 +73,6 @@ describe('Terminal', () => { }); test('cannot write after disposal', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); term.dispose(); @@ -83,8 +81,6 @@ describe('Terminal', () => { }); test('cannot open twice', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -94,8 +90,6 @@ describe('Terminal', () => { }); test('cannot open after disposal', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); term.dispose(); @@ -111,8 +105,6 @@ describe('Terminal', () => { }); test('exposes element after open', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); expect(term.element).toBeUndefined(); @@ -149,8 +141,6 @@ describe('Terminal', () => { }); test('onResize fires when terminal is resized', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal({ cols: 80, rows: 24 }); await term.open(container); @@ -169,8 +159,6 @@ describe('Terminal', () => { }); test('onBell fires on bell character', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -192,8 +180,6 @@ describe('Terminal', () => { describe('Writing', () => { test('write() does not throw after open', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -203,8 +189,6 @@ describe('Terminal', () => { }); test('write() accepts string', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -214,8 +198,6 @@ describe('Terminal', () => { }); test('write() accepts Uint8Array', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -226,8 +208,6 @@ describe('Terminal', () => { }); test('writeln() adds newline', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -239,8 +219,6 @@ describe('Terminal', () => { describe('Resizing', () => { test('resize() updates dimensions', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal({ cols: 80, rows: 24 }); await term.open(container); @@ -253,8 +231,6 @@ describe('Terminal', () => { }); test('resize() with same dimensions is no-op', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal({ cols: 80, rows: 24 }); await term.open(container); @@ -276,8 +252,6 @@ describe('Terminal', () => { describe('Control Methods', () => { test('clear() does not throw', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -287,8 +261,6 @@ describe('Terminal', () => { }); test('reset() does not throw', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -298,8 +270,6 @@ describe('Terminal', () => { }); test('focus() does not throw', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -316,8 +286,6 @@ describe('Terminal', () => { describe('Addons', () => { test('loadAddon() accepts addon', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -336,8 +304,6 @@ describe('Terminal', () => { }); test('loadAddon() calls activate', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -357,8 +323,6 @@ describe('Terminal', () => { }); test('dispose() calls addon dispose', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -379,8 +343,6 @@ describe('Terminal', () => { describe('Integration', () => { test('can write ANSI sequences', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -393,8 +355,6 @@ describe('Terminal', () => { }); test('can handle cursor movement sequences', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -406,8 +366,6 @@ describe('Terminal', () => { }); test('multiple write calls work', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -423,8 +381,6 @@ describe('Terminal', () => { describe('Disposal', () => { test('dispose() can be called multiple times', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container); @@ -433,8 +389,6 @@ describe('Terminal', () => { }); test('dispose() cleans up canvas element', async () => { - if (!container) throw new Error('DOM environment not available - check happydom setup'); - const term = new Terminal(); await term.open(container);