Vimcraft Docs / vim.e2e - End-to-End Testing
vim.e2e - End-to-End Testing API
Jest-style testing framework for plugin development. Test cursor movements, mode changes, buffer content, and terminal rendering.
Running Tests
# Run tests in a directory vimc test tests/ # Run a single test file vimc test tests/my-plugin.ts
Basic Test Structure
describe() and test()
vim.e2e.describe('Motion Commands', function() { vim.e2e.test('j moves cursor down', function() { vim.e2e.keys('j'); vim.e2e.assert.cursorAt(1, 0); }); vim.e2e.test('w moves to next word', function() { vim.e2e.keys('w'); const cursor = vim.e2e.getCursor(); vim.e2e.assert.true(cursor.col > 0, 'Cursor should move forward'); }); }); // Run all tests vim.e2e.runAll();
Simulating Input
keys()
Send key sequences to the editor:
// Basic movement vim.e2e.keys('j'); // Down vim.e2e.keys('k'); // Up vim.e2e.keys('gg'); // Go to top // Enter insert mode and type vim.e2e.keys('i'); // Enter insert mode vim.e2e.keys('Hello'); // Type text vim.e2e.keys('<Esc>'); // Exit insert mode // Complex sequence vim.e2e.keys('iHello World<Esc>0w'); // Insert, escape, go to word
Assertions
Basic Assertions
// Equality vim.e2e.assert.equal(actual, expected, 'message'); vim.e2e.assert.deepEqual(obj1, obj2); // Boolean vim.e2e.assert.true(condition, 'should be true'); vim.e2e.assert.false(condition, 'should be false'); // Null checks vim.e2e.assert.null(value); vim.e2e.assert.notNull(value);
Editor-Specific Assertions
// Check mode vim.e2e.assert.mode('NORMAL'); vim.e2e.assert.mode('INSERT'); vim.e2e.assert.mode('VISUAL'); // Check cursor position (0-indexed) vim.e2e.assert.cursorAt(0, 0); // Line 0, Column 0 vim.e2e.assert.cursorAt(5, 10); // Line 5, Column 10 // Check buffer content vim.e2e.assert.bufferContains('Hello'); vim.e2e.assert.bufferContains('function');
Getting Editor State
getCursor()
const cursor = vim.e2e.getCursor(); console.log(`Line: ${cursor.line}, Col: ${cursor.col}`);
getMode()
const mode = vim.e2e.getMode(); console.log(`Current mode: ${mode}`); // 'NORMAL', 'INSERT', 'VISUAL', 'VISUAL_LINE', etc.
getState()
Get complete editor state:
const state = vim.e2e.getState(); console.log(`Mode: ${state.mode}`); console.log(`Cursor: ${state.cursor.line}, ${state.cursor.col}`); console.log(`Lines: ${state.buffer.lineCount}`); console.log(`Changes: ${state.buffer.changedTick}`);
PTY Testing
Test terminal rendering and escape codes:
Capture Terminal Output
vim.e2e.describe('Rendering', function() { vim.e2e.test('minimal cursor updates', function() { vim.e2e.pty.startCapture(); vim.e2e.keys('jjjjj'); vim.e2e.pty.render(); vim.e2e.pty.stopCapture(); const stats = vim.e2e.pty.getRenderStats(); vim.e2e.assert.true( stats.cursorPositionCodes < 10, 'Too many cursor position updates' ); }); });
PTY Methods
// Control capture vim.e2e.pty.startCapture(); vim.e2e.pty.stopCapture(); vim.e2e.pty.isCapturing(); vim.e2e.pty.clear(); // Trigger rendering vim.e2e.pty.render(); // Get output const output = vim.e2e.pty.getOutput(); const length = vim.e2e.pty.getLength(); // Count escape codes vim.e2e.pty.countSGRCodes(); // Color codes vim.e2e.pty.countHideCursor(); // Cursor hide vim.e2e.pty.countShowCursor(); // Cursor show vim.e2e.pty.countCursorPositionCodes(); vim.e2e.pty.countSequence('\x1b[2J'); // Custom sequence // Get statistics const stats = vim.e2e.pty.getRenderStats();
Complete Test Example
// tests/motions.ts vim.e2e.describe('Basic Motions', function() { vim.e2e.test('j/k moves up and down', function() { vim.e2e.keys('j'); vim.e2e.assert.cursorAt(1, 0); vim.e2e.keys('k'); vim.e2e.assert.cursorAt(0, 0); }); vim.e2e.test('h/l moves left and right', function() { vim.e2e.keys('l'); vim.e2e.assert.cursorAt(0, 1); vim.e2e.keys('h'); vim.e2e.assert.cursorAt(0, 0); }); }); vim.e2e.describe('Insert Mode', function() { vim.e2e.test('i enters insert mode', function() { vim.e2e.keys('i'); vim.e2e.assert.mode('INSERT'); }); vim.e2e.test('Esc returns to normal', function() { vim.e2e.keys('i'); vim.e2e.keys('<Esc>'); vim.e2e.assert.mode('NORMAL'); }); vim.e2e.test('typing inserts text', function() { vim.e2e.keys('iHello<Esc>'); vim.e2e.assert.bufferContains('Hello'); }); }); vim.e2e.describe('Rendering Performance', function() { vim.e2e.test('no cursor flicker on movement', function() { vim.e2e.pty.startCapture(); vim.e2e.keys('jjjjj'); vim.e2e.pty.render(); vim.e2e.pty.stopCapture(); const hideCount = vim.e2e.pty.countHideCursor(); vim.e2e.assert.true(hideCount < 3, 'Cursor should not flicker'); }); }); // Run and report const result = vim.e2e.runAll(); console.log(`Tests: ${result.passed} passed, ${result.failed} failed`);
Debugging Tests
checkpoint()
Add markers in logs:
vim.e2e.test('complex operation', function() { vim.e2e.checkpoint('Before motion'); vim.e2e.keys('gg'); vim.e2e.checkpoint('After goto top'); vim.e2e.keys('G'); vim.e2e.checkpoint('After goto bottom'); });
getLogs()
Retrieve debug logs:
const logs = vim.e2e.getLogs({ level: 'debug', maxBytes: 4096 }); console.log(logs);
getLayers()
Inspect compositor layers:
const layers = vim.e2e.getLayers(); for (const layer of layers) { console.log(`${layer.name}: enabled=${layer.enabled}, cells=${layer.cells}`); }
See Also
- CLI Commands - Running tests with vimc test
- Plugin Development - Building testable plugins
- vim.api - Editor APIs to test