scroll/src/common/all_test.ts

242 lines
6.4 KiB
JavaScript

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();
const testKeyMap = (codes: string[], expected: string) => {
codes.forEach((code) => {
assertEquals(readKey(code), expected);
});
};
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);
// And pass through whatever else
assertEquals(readKey('foobaz'), 'foobaz');
},
'readKey Esc': () =>
testKeyMap(['\x1b', Util.ctrlKey('l')], KeyCommand.Escape),
'readKey Backspace': () =>
testKeyMap(
[Util.ctrlKey('h'), '\x7f'],
KeyCommand.Backspace,
),
'readKey Home': () =>
testKeyMap(['\x1b[1~', '\x1b[7~', '\x1b[H', '\x1bOH'], KeyCommand.Home),
'readKey End': () =>
testKeyMap(['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'], 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);
},
'Document.delete': () => {
const doc = Document.empty();
doc.insert(Position.default(), 'foobar');
doc.delete(Position.at(3, 0));
assertEquals(doc.row(0)?.toString(), 'fooar');
},
},
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), '👨‍👩‍👧');
},
},
});