import { Ansi, KeyCommand, readKey } from './ansi.ts'; import Buffer from './buffer.ts'; import Document from './document.ts'; import Editor from './editor.ts'; import Row from './row.ts'; import { getTestRunner } from './runtime.ts'; import { defaultTerminalSize } from './termios.ts'; import { Position } from './types.ts'; import * as Util from './utils.ts'; const { assertEquals, assertExists, assertInstanceOf, assertNotEquals, assertFalse, assertTrue, testSuite, } = await getTestRunner(); testSuite({ 'ANSI::ANSI utils': { 'Ansi.moveCursor': () => { assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H'); }, 'Ansi.moveCursorForward': () => { assertEquals(Ansi.moveCursorForward(2), '\x1b[2C'); }, 'Ansi.moveCursorDown': () => { assertEquals(Ansi.moveCursorDown(7), '\x1b[7B'); }, }, 'ANSI::readKey()': { 'readKey passthrough': () => { // Ignore unhandled escape sequences assertEquals(readKey('\x1b[]'), '\x1b[]'); // Pass explicitly mapped values right through assertEquals(readKey(KeyCommand.ArrowUp), KeyCommand.ArrowUp); assertEquals(readKey(KeyCommand.Home), KeyCommand.Home); assertEquals(readKey(KeyCommand.Delete), KeyCommand.Delete); }, 'readKey Esc': () => { ['\x1b', Util.ctrlKey('l')].forEach((code) => { assertEquals(readKey(code), KeyCommand.Escape); }); }, 'readKey Backspace': () => { [Util.ctrlKey('h'), String.fromCodePoint(127)].forEach((code) => { assertEquals(readKey(code), KeyCommand.Backspace); }); }, 'readKey Home': () => { ['\x1b[1~', '\x1b[7~', '\x1b[H', '\x1bOH'].forEach((code) => { assertEquals(readKey(code), KeyCommand.Home); }); }, 'readKey End': () => { ['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'].forEach((code) => { assertEquals(readKey(code), KeyCommand.End); }); }, }, Buffer: { 'Buffer exists': () => { const b = new Buffer(); assertInstanceOf(b, Buffer); assertEquals(b.strlen(), 0); }, 'Buffer.appendLine': () => { const b = new Buffer(); // Carriage return and line feed b.appendLine(); assertEquals(b.strlen(), 2); b.clear(); assertEquals(b.strlen(), 0); b.appendLine('foo'); assertEquals(b.strlen(), 5); }, 'Buffer.append': () => { const b = new Buffer(); b.append('foobar'); assertEquals(b.strlen(), 6); b.clear(); b.append('foobar', 3); assertEquals(b.strlen(), 3); }, 'Buffer.flush': async () => { const b = new Buffer(); b.appendLine('foobarbaz' + Ansi.ClearLine); assertEquals(b.strlen(), 14); await b.flush(); assertEquals(b.strlen(), 0); }, }, Document: { 'Document.empty': () => { const doc = Document.empty(); assertEquals(doc.numRows, 0); assertTrue(doc.isEmpty()); assertEquals(doc.row(0), null); }, 'Document.appendRow': () => { const doc = Document.empty(); doc.appendRow('foobar'); assertEquals(doc.numRows, 1); assertFalse(doc.isEmpty()); assertInstanceOf(doc.row(0), Row); }, 'Document.insert': () => { const doc = Document.empty(); assertFalse(doc.dirty); doc.insert(Position.at(0, 0), 'foobar'); assertEquals(doc.numRows, 1); assertTrue(doc.dirty); doc.insert(Position.at(2, 0), 'baz'); assertEquals(doc.numRows, 1); assertTrue(doc.dirty); doc.insert(Position.at(9, 0), 'buzz'); assertEquals(doc.numRows, 1); assertTrue(doc.dirty); const row0 = doc.row(0); assertEquals(row0?.toString(), 'foobazbarbuzz'); assertEquals(row0?.rstring(), 'foobazbarbuzz'); assertEquals(row0?.rsize, 13); doc.insert(Position.at(0, 1), 'Lorem Ipsum'); assertEquals(doc.numRows, 2); assertTrue(doc.dirty); }, }, Editor: { 'new Editor': () => { const e = new Editor(defaultTerminalSize); assertInstanceOf(e, Editor); }, }, Position: { 'default': () => { const p = Position.default(); assertEquals(p.x, 0); assertEquals(p.y, 0); }, 'at': () => { const p = Position.at(5, 7); assertEquals(p.x, 5); assertEquals(p.y, 7); }, }, Row: { 'new Row': () => { const row = new Row(); assertEquals(row.toString(), ''); }, }, 'Util misc fns': { 'noop fn': () => { assertExists(Util.noop); assertEquals(Util.noop(), undefined); }, 'posSub()': () => { assertEquals(Util.posSub(14, 15), 0); assertEquals(Util.posSub(15, 1), 14); }, 'minSub()': () => { assertEquals(Util.minSub(13, 25, -1), -1); assertEquals(Util.minSub(25, 13, 0), 12); }, 'maxAdd()': () => { assertEquals(Util.maxAdd(99, 99, 75), 75); assertEquals(Util.maxAdd(25, 74, 101), 99); }, }, 'Util string fns': { 'ord()': () => { // Invalid output assertEquals(Util.ord(''), 256); // Valid output assertEquals(Util.ord('a'), 97); }, 'chars() properly splits strings into unicode characters': () => { assertEquals(Util.chars('😺😸😹'), ['😺', '😸', '😹']); }, 'ctrl_key()': () => { const ctrl_a = Util.ctrlKey('a'); assertTrue(Util.isControl(ctrl_a)); assertEquals(ctrl_a, String.fromCodePoint(0x01)); const invalid = Util.ctrlKey('😺'); assertFalse(Util.isControl(invalid)); assertEquals(invalid, '😺'); }, 'is_ascii()': () => { assertTrue(Util.isAscii('asjyverkjhsdf1928374')); assertFalse(Util.isAscii('😺acalskjsdf')); assertFalse(Util.isAscii('ab😺ac')); }, 'is_control()': () => { assertFalse(Util.isControl('abc')); assertTrue(Util.isControl(String.fromCodePoint(0x01))); assertFalse(Util.isControl('😺')); }, 'strlen()': () => { // Ascii length assertEquals(Util.strlen('abc'), 'abc'.length); // Get number of visible unicode characters assertEquals(Util.strlen('😺😸😹'), 3); assertNotEquals('😺😸😹'.length, Util.strlen('😺😸😹')); // Skin tone modifier + base character assertEquals(Util.strlen('🀰🏼'), 2); assertNotEquals('🀰🏼'.length, Util.strlen('🀰🏼')); // This has 4 sub-characters, and 3 zero-width-joiners assertEquals(Util.strlen('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦'), 7); assertNotEquals('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦'.length, Util.strlen('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦')); }, 'truncate()': () => { assertEquals(Util.truncate('😺😸😹', 1), '😺'); assertEquals(Util.truncate('😺😸😹', 5), '😺😸😹'); assertEquals(Util.truncate('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦', 5), 'πŸ‘¨β€πŸ‘©β€πŸ‘§'); }, }, });