2023-11-16 20:57:21 -05:00
|
|
|
import Buffer from './buffer.ts';
|
2023-11-21 15:14:08 -05:00
|
|
|
import Document from './document.ts';
|
|
|
|
import Editor from './editor.ts';
|
|
|
|
import Row from './row.ts';
|
2023-11-29 16:09:58 -05:00
|
|
|
import { Ansi, KeyCommand } from './ansi.ts';
|
|
|
|
import { defaultTerminalSize } from './config.ts';
|
|
|
|
import { getTestRunner } from './runtime.ts';
|
2023-11-21 16:36:13 -05:00
|
|
|
import { Position } from './types.ts';
|
2023-11-29 16:09:58 -05:00
|
|
|
import * as Util from './fns.ts';
|
2023-11-16 20:57:21 -05:00
|
|
|
|
2023-11-16 21:22:24 -05:00
|
|
|
const {
|
|
|
|
assertEquals,
|
|
|
|
assertExists,
|
|
|
|
assertInstanceOf,
|
|
|
|
assertNotEquals,
|
|
|
|
assertFalse,
|
|
|
|
assertTrue,
|
|
|
|
testSuite,
|
|
|
|
} = await getTestRunner();
|
2023-11-16 20:57:21 -05:00
|
|
|
|
2023-11-24 08:31:51 -05:00
|
|
|
const encoder = new TextEncoder();
|
2023-11-22 11:07:33 -05:00
|
|
|
const testKeyMap = (codes: string[], expected: string) => {
|
|
|
|
codes.forEach((code) => {
|
2023-11-29 16:09:58 -05:00
|
|
|
assertEquals(Util.readKey(encoder.encode(code)), expected);
|
2023-11-22 11:07:33 -05:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2023-11-21 15:14:08 -05:00
|
|
|
testSuite({
|
2023-11-29 16:09:58 -05:00
|
|
|
'ANSI utils': {
|
|
|
|
'moveCursor()': () => {
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H');
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'moveCursorForward()': () => {
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(Ansi.moveCursorForward(2), '\x1b[2C');
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'moveCursorDown()': () => {
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(Ansi.moveCursorDown(7), '\x1b[7B');
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Buffer: {
|
2023-11-29 16:09:58 -05:00
|
|
|
'new Buffer': () => {
|
2023-11-16 20:57:21 -05:00
|
|
|
const b = new Buffer();
|
2023-11-16 21:22:24 -05:00
|
|
|
assertInstanceOf(b, Buffer);
|
|
|
|
assertEquals(b.strlen(), 0);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.appendLine': () => {
|
2023-11-16 20:57:21 -05:00
|
|
|
const b = new Buffer();
|
|
|
|
|
|
|
|
// Carriage return and line feed
|
|
|
|
b.appendLine();
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(b.strlen(), 2);
|
2023-11-16 20:57:21 -05:00
|
|
|
|
|
|
|
b.clear();
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(b.strlen(), 0);
|
2023-11-16 20:57:21 -05:00
|
|
|
|
|
|
|
b.appendLine('foo');
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(b.strlen(), 5);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.append': () => {
|
2023-11-16 20:57:21 -05:00
|
|
|
const b = new Buffer();
|
|
|
|
|
|
|
|
b.append('foobar');
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(b.strlen(), 6);
|
2023-11-16 20:57:21 -05:00
|
|
|
b.clear();
|
|
|
|
|
|
|
|
b.append('foobar', 3);
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(b.strlen(), 3);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.flush': async () => {
|
2023-11-16 20:57:21 -05:00
|
|
|
const b = new Buffer();
|
|
|
|
b.appendLine('foobarbaz' + Ansi.ClearLine);
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(b.strlen(), 14);
|
2023-11-16 20:57:21 -05:00
|
|
|
|
|
|
|
await b.flush();
|
|
|
|
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(b.strlen(), 0);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Document: {
|
2023-11-29 16:09:58 -05:00
|
|
|
'.default': () => {
|
2023-11-27 10:25:30 -05:00
|
|
|
const doc = Document.default();
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(doc.numRows, 0);
|
|
|
|
assertTrue(doc.isEmpty());
|
|
|
|
assertEquals(doc.row(0), null);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.insertRow': () => {
|
2023-11-27 10:25:30 -05:00
|
|
|
const doc = Document.default();
|
2023-11-22 17:09:41 -05:00
|
|
|
doc.insertRow(undefined, 'foobar');
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(doc.numRows, 1);
|
|
|
|
assertFalse(doc.isEmpty());
|
|
|
|
assertInstanceOf(doc.row(0), Row);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.insert': () => {
|
2023-11-27 10:25:30 -05:00
|
|
|
const doc = Document.default();
|
2023-11-21 16:36:13 -05:00
|
|
|
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);
|
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.delete': () => {
|
2023-11-27 10:25:30 -05:00
|
|
|
const doc = Document.default();
|
2023-11-22 11:07:33 -05:00
|
|
|
doc.insert(Position.default(), 'foobar');
|
|
|
|
doc.delete(Position.at(3, 0));
|
|
|
|
assertEquals(doc.row(0)?.toString(), 'fooar');
|
|
|
|
},
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
|
|
|
Editor: {
|
|
|
|
'new Editor': () => {
|
|
|
|
const e = new Editor(defaultTerminalSize);
|
2023-11-16 21:22:24 -05:00
|
|
|
assertInstanceOf(e, Editor);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
|
|
|
},
|
2023-11-21 16:36:13 -05:00
|
|
|
Position: {
|
2023-11-29 16:09:58 -05:00
|
|
|
'.default': () => {
|
2023-11-21 16:36:13 -05:00
|
|
|
const p = Position.default();
|
|
|
|
assertEquals(p.x, 0);
|
|
|
|
assertEquals(p.y, 0);
|
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.at': () => {
|
2023-11-21 16:36:13 -05:00
|
|
|
const p = Position.at(5, 7);
|
|
|
|
assertEquals(p.x, 5);
|
|
|
|
assertEquals(p.y, 7);
|
|
|
|
},
|
|
|
|
},
|
2023-11-21 15:14:08 -05:00
|
|
|
Row: {
|
2023-11-29 16:09:58 -05:00
|
|
|
'.default': () => {
|
2023-11-22 17:09:41 -05:00
|
|
|
const row = Row.default();
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(row.toString(), '');
|
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'.from': () => {
|
2023-11-22 17:09:41 -05:00
|
|
|
// From string
|
|
|
|
const row = Row.from('xyz');
|
|
|
|
assertEquals(row.toString(), 'xyz');
|
|
|
|
|
|
|
|
// From existing Row
|
|
|
|
assertEquals(Row.from(row).toString(), row.toString());
|
|
|
|
|
|
|
|
// From 'chars'
|
|
|
|
assertEquals(Row.from(['😺', '😸', '😹']).toString(), '😺😸😹');
|
|
|
|
},
|
2023-11-21 15:14:08 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'fns': {
|
2023-11-22 17:09:41 -05:00
|
|
|
'arrayInsert() strings': () => {
|
|
|
|
const a = ['😺', '😸', '😹'];
|
|
|
|
const b = Util.arrayInsert(a, 1, 'x');
|
|
|
|
const c = ['😺', 'x', '😸', '😹'];
|
|
|
|
assertEquals(b, c);
|
|
|
|
|
|
|
|
const d = Util.arrayInsert(c, 17, 'y');
|
|
|
|
const e = ['😺', 'x', '😸', '😹', 'y'];
|
|
|
|
assertEquals(d, e);
|
|
|
|
|
|
|
|
assertEquals(Util.arrayInsert([], 0, 'foo'), ['foo']);
|
|
|
|
},
|
|
|
|
'arrayInsert() numbers': () => {
|
|
|
|
const a = [1, 3, 5];
|
|
|
|
const b = [1, 3, 4, 5];
|
|
|
|
assertEquals(Util.arrayInsert(a, 2, 4), b);
|
|
|
|
|
|
|
|
const c = [1, 2, 3, 4, 5];
|
|
|
|
assertEquals(Util.arrayInsert(b, 1, 2), c);
|
|
|
|
},
|
2023-11-16 20:57:21 -05:00
|
|
|
'noop fn': () => {
|
2023-11-21 15:14:08 -05:00
|
|
|
assertExists(Util.noop);
|
|
|
|
assertEquals(Util.noop(), undefined);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-21 16:36:13 -05:00
|
|
|
'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);
|
|
|
|
},
|
2023-11-16 21:22:24 -05:00
|
|
|
'ord()': () => {
|
|
|
|
// Invalid output
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.ord(''), 256);
|
2023-11-16 21:22:24 -05:00
|
|
|
|
|
|
|
// Valid output
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.ord('a'), 97);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
|
|
|
'chars() properly splits strings into unicode characters': () => {
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.chars('😺😸😹'), ['😺', '😸', '😹']);
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'ctrlKey()': () => {
|
2023-11-21 15:14:08 -05:00
|
|
|
const ctrl_a = Util.ctrlKey('a');
|
|
|
|
assertTrue(Util.isControl(ctrl_a));
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(ctrl_a, String.fromCodePoint(0x01));
|
2023-11-16 20:57:21 -05:00
|
|
|
|
2023-11-21 15:14:08 -05:00
|
|
|
const invalid = Util.ctrlKey('😺');
|
|
|
|
assertFalse(Util.isControl(invalid));
|
2023-11-16 21:22:24 -05:00
|
|
|
assertEquals(invalid, '😺');
|
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'isAscii()': () => {
|
2023-11-21 15:14:08 -05:00
|
|
|
assertTrue(Util.isAscii('asjyverkjhsdf1928374'));
|
|
|
|
assertFalse(Util.isAscii('😺acalskjsdf'));
|
|
|
|
assertFalse(Util.isAscii('ab😺ac'));
|
2023-11-16 21:22:24 -05:00
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'isControl()': () => {
|
2023-11-21 15:14:08 -05:00
|
|
|
assertFalse(Util.isControl('abc'));
|
|
|
|
assertTrue(Util.isControl(String.fromCodePoint(0x01)));
|
|
|
|
assertFalse(Util.isControl('😺'));
|
2023-11-16 21:22:24 -05:00
|
|
|
},
|
|
|
|
'strlen()': () => {
|
|
|
|
// Ascii length
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.strlen('abc'), 'abc'.length);
|
2023-11-16 21:22:24 -05:00
|
|
|
|
|
|
|
// Get number of visible unicode characters
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.strlen('😺😸😹'), 3);
|
|
|
|
assertNotEquals('😺😸😹'.length, Util.strlen('😺😸😹'));
|
2023-11-16 20:57:21 -05:00
|
|
|
|
|
|
|
// Skin tone modifier + base character
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.strlen('🤰🏼'), 2);
|
|
|
|
assertNotEquals('🤰🏼'.length, Util.strlen('🤰🏼'));
|
2023-11-16 20:57:21 -05:00
|
|
|
|
|
|
|
// This has 4 sub-characters, and 3 zero-width-joiners
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.strlen('👨👩👧👦'), 7);
|
|
|
|
assertNotEquals('👨👩👧👦'.length, Util.strlen('👨👩👧👦'));
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
2023-11-16 21:22:24 -05:00
|
|
|
'truncate()': () => {
|
2023-11-21 15:14:08 -05:00
|
|
|
assertEquals(Util.truncate('😺😸😹', 1), '😺');
|
|
|
|
assertEquals(Util.truncate('😺😸😹', 5), '😺😸😹');
|
|
|
|
assertEquals(Util.truncate('👨👩👧👦', 5), '👨👩👧');
|
2023-11-16 20:57:21 -05:00
|
|
|
},
|
|
|
|
},
|
2023-11-29 16:09:58 -05:00
|
|
|
'readKey()': {
|
|
|
|
'empty input': () => {
|
|
|
|
assertEquals(Util.readKey(new Uint8Array(0)), '');
|
|
|
|
},
|
|
|
|
'passthrough': () => {
|
|
|
|
// Ignore unhandled escape sequences
|
|
|
|
assertEquals(Util.readKey(encoder.encode('\x1b[]')), '\x1b[]');
|
|
|
|
|
|
|
|
// Pass explicitly mapped values right through
|
|
|
|
assertEquals(
|
|
|
|
Util.readKey(encoder.encode(KeyCommand.ArrowUp)),
|
|
|
|
KeyCommand.ArrowUp,
|
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
Util.readKey(encoder.encode(KeyCommand.Home)),
|
|
|
|
KeyCommand.Home,
|
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
Util.readKey(encoder.encode(KeyCommand.Delete)),
|
|
|
|
KeyCommand.Delete,
|
|
|
|
);
|
|
|
|
|
|
|
|
// And pass through whatever else
|
|
|
|
assertEquals(Util.readKey(encoder.encode('foobaz')), 'foobaz');
|
|
|
|
},
|
|
|
|
|
|
|
|
'Esc': () => testKeyMap(['\x1b', Util.ctrlKey('l')], KeyCommand.Escape),
|
|
|
|
'Backspace': () =>
|
|
|
|
testKeyMap(
|
|
|
|
[Util.ctrlKey('h'), '\x7f'],
|
|
|
|
KeyCommand.Backspace,
|
|
|
|
),
|
|
|
|
'Home': () =>
|
|
|
|
testKeyMap(['\x1b[1~', '\x1b[7~', '\x1b[H', '\x1bOH'], KeyCommand.Home),
|
|
|
|
'End': () =>
|
|
|
|
testKeyMap(['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'], KeyCommand.End),
|
|
|
|
'Enter': () => testKeyMap(['\n', '\r', '\v'], KeyCommand.Enter),
|
|
|
|
},
|
2023-11-21 15:14:08 -05:00
|
|
|
});
|