mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
1. Regex global flag safety — strip `g` flag before matching to prevent
lastIndex mutation and missing .index on match results
2. state.visible false before activate — null overlay no longer reports
visible: true
3. removeChar() returns 'pending' | 'flushed' | false instead of boolean
so consumers can distinguish whether to send backspace to PTY
4. removeChar() implements buffer detection cascade (step 3) — detects
existing prompt text when both pending and flushed are empty
5. RenderKey includes text content, not just length — prevents stale
renders when setFlushed() called with same count but different text
6. Remove dead `import type { Terminal }` from test file
7. Remove internal `FontStyle` from public exports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
6.5 KiB
TypeScript
163 lines
6.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { createMockTerminal } from './helpers.js';
|
|
import { findPrompt, readTextAfterPrompt } from '../src/prompt-finder.js';
|
|
import type { XtermTerminal, PromptFinder } from '../src/types.js';
|
|
|
|
function term(lines: string[]) {
|
|
return createMockTerminal({ buffer: { lines } });
|
|
}
|
|
|
|
describe('findPrompt', () => {
|
|
describe('character strategy', () => {
|
|
it('finds $ prompt at column 0', () => {
|
|
const { terminal, cleanup } = term(['output line', '$ ls -la']);
|
|
const finder: PromptFinder = { type: 'character', char: '$' };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toEqual({ row: 1, col: 0 });
|
|
cleanup();
|
|
});
|
|
|
|
it('finds > prompt', () => {
|
|
const { terminal, cleanup } = term(['> hello']);
|
|
const finder: PromptFinder = { type: 'character', char: '>' };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toEqual({ row: 0, col: 0 });
|
|
cleanup();
|
|
});
|
|
|
|
it('finds prompt with prefix (user@host)', () => {
|
|
const { terminal, cleanup } = term(['user@host:~$ command']);
|
|
const finder: PromptFinder = { type: 'character', char: '$' };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toEqual({ row: 0, col: 11 });
|
|
cleanup();
|
|
});
|
|
|
|
it('scans bottom-up and returns lowest match', () => {
|
|
const { terminal, cleanup } = term([
|
|
'$ old prompt',
|
|
'output',
|
|
'$ current prompt',
|
|
]);
|
|
const finder: PromptFinder = { type: 'character', char: '$' };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toEqual({ row: 2, col: 0 });
|
|
cleanup();
|
|
});
|
|
|
|
it('returns null when no prompt found', () => {
|
|
const { terminal, cleanup } = term(['no prompt here', 'or here']);
|
|
const finder: PromptFinder = { type: 'character', char: '$' };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toBeNull();
|
|
cleanup();
|
|
});
|
|
|
|
it('finds Unicode prompt character', () => {
|
|
const { terminal, cleanup } = term(['\u276f hello']);
|
|
const finder: PromptFinder = { type: 'character', char: '\u276f' };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toEqual({ row: 0, col: 0 });
|
|
cleanup();
|
|
});
|
|
});
|
|
|
|
describe('regex strategy', () => {
|
|
it('finds regex prompt', () => {
|
|
const { terminal, cleanup } = term(['user@host:~/dir$ ls']);
|
|
const finder: PromptFinder = { type: 'regex', pattern: /\$/ };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).not.toBeNull();
|
|
expect(pos!.col).toBe(15);
|
|
cleanup();
|
|
});
|
|
|
|
it('matches complex PS1 patterns', () => {
|
|
const { terminal, cleanup } = term(['(venv) user % cmd']);
|
|
const finder: PromptFinder = { type: 'regex', pattern: /%/ };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).not.toBeNull();
|
|
expect(pos!.col).toBe(12);
|
|
cleanup();
|
|
});
|
|
|
|
it('returns null on no match', () => {
|
|
const { terminal, cleanup } = term(['just output']);
|
|
const finder: PromptFinder = { type: 'regex', pattern: /\$\s*$/ };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toBeNull();
|
|
cleanup();
|
|
});
|
|
|
|
it('handles global flag safely (strips g to avoid lastIndex)', () => {
|
|
const { terminal, cleanup } = term(['user@host:~$ cmd']);
|
|
const finder: PromptFinder = { type: 'regex', pattern: /\$/g };
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).not.toBeNull();
|
|
expect(pos!.col).toBe(11);
|
|
// Call again — should return same result (no lastIndex drift)
|
|
const pos2 = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos2).toEqual(pos);
|
|
cleanup();
|
|
});
|
|
});
|
|
|
|
describe('custom strategy', () => {
|
|
it('uses custom finder function', () => {
|
|
const { terminal, cleanup } = term(['anything']);
|
|
const finder: PromptFinder = {
|
|
type: 'custom',
|
|
find: () => ({ row: 5, col: 10 }),
|
|
};
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toEqual({ row: 5, col: 10 });
|
|
cleanup();
|
|
});
|
|
|
|
it('handles null from custom finder', () => {
|
|
const { terminal, cleanup } = term(['anything']);
|
|
const finder: PromptFinder = {
|
|
type: 'custom',
|
|
find: () => null,
|
|
};
|
|
const pos = findPrompt(terminal as unknown as XtermTerminal, finder);
|
|
expect(pos).toBeNull();
|
|
cleanup();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('readTextAfterPrompt', () => {
|
|
it('reads text after prompt with offset', () => {
|
|
const { terminal, cleanup } = term(['$ hello world']);
|
|
const prompt = { row: 0, col: 0 };
|
|
const text = readTextAfterPrompt(terminal as unknown as XtermTerminal, prompt, 2);
|
|
expect(text).toBe('hello world');
|
|
cleanup();
|
|
});
|
|
|
|
it('returns empty string for empty prompt line', () => {
|
|
const { terminal, cleanup } = term(['$ ']);
|
|
const prompt = { row: 0, col: 0 };
|
|
const text = readTextAfterPrompt(terminal as unknown as XtermTerminal, prompt, 2);
|
|
expect(text).toBe('');
|
|
cleanup();
|
|
});
|
|
|
|
it('trims trailing whitespace', () => {
|
|
const { terminal, cleanup } = term(['$ hello ']);
|
|
const prompt = { row: 0, col: 0 };
|
|
const text = readTextAfterPrompt(terminal as unknown as XtermTerminal, prompt, 2);
|
|
expect(text).toBe('hello');
|
|
cleanup();
|
|
});
|
|
|
|
it('handles offset for complex prompts', () => {
|
|
const { terminal, cleanup } = term(['user@host:~$ ls -la']);
|
|
const prompt = { row: 0, col: 11 };
|
|
const text = readTextAfterPrompt(terminal as unknown as XtermTerminal, prompt, 2);
|
|
expect(text).toBe('ls -la');
|
|
cleanup();
|
|
});
|
|
});
|