All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
718 lines
19 KiB
JavaScript
718 lines
19 KiB
JavaScript
import Ansi, * as _Ansi from './ansi.ts';
|
|
import Buffer from './buffer.ts';
|
|
import Document from './document.ts';
|
|
import Editor from './editor.ts';
|
|
import { FileLang } from './filetype.ts';
|
|
import Option, { None, Some } from './option.ts';
|
|
import Position from './position.ts';
|
|
import Row from './row.ts';
|
|
|
|
import FileType, * as FT from './filetype.ts';
|
|
import * as Fn from './fns.ts';
|
|
import { defaultTerminalSize, SCROLL_TAB_SIZE } from './config.ts';
|
|
import { getTestRunner } from './runtime.ts';
|
|
import { HighlightType, SearchDirection } from './types.ts';
|
|
|
|
import fs from 'node:fs';
|
|
|
|
const {
|
|
assertEquals,
|
|
assertEquivalent,
|
|
assertExists,
|
|
assertInstanceOf,
|
|
assertNotEquals,
|
|
assertFalse,
|
|
assertTrue,
|
|
assertSome,
|
|
assertNone,
|
|
testSuite,
|
|
} = await getTestRunner();
|
|
|
|
const THIS_FILE = './src/common/all_test.ts';
|
|
const KILO_FILE = './demo/kilo.c';
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Helper Function Tests
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const fnTest = () => {
|
|
const {
|
|
arrayInsert,
|
|
noop,
|
|
posSub,
|
|
minSub,
|
|
maxAdd,
|
|
ord,
|
|
strChars,
|
|
ctrlKey,
|
|
isControl,
|
|
isAscii,
|
|
isAsciiDigit,
|
|
strlen,
|
|
truncate,
|
|
highlightToColor,
|
|
} = Fn;
|
|
|
|
return {
|
|
'arrayInsert() strings': () => {
|
|
const a = ['😺', '😸', '😹'];
|
|
const b = arrayInsert(a, 1, 'x');
|
|
const c = ['😺', 'x', '😸', '😹'];
|
|
assertEquivalent(b, c);
|
|
|
|
const d = arrayInsert(c, 17, 'y');
|
|
const e = ['😺', 'x', '😸', '😹', 'y'];
|
|
assertEquivalent(d, e);
|
|
|
|
assertEquivalent(arrayInsert([], 0, 'foo'), ['foo']);
|
|
},
|
|
'arrayInsert() numbers': () => {
|
|
const a = [1, 3, 5];
|
|
const b = [1, 3, 4, 5];
|
|
assertEquivalent(arrayInsert(a, 2, 4), b);
|
|
|
|
const c = [1, 2, 3, 4, 5];
|
|
assertEquivalent(arrayInsert(b, 1, 2), c);
|
|
},
|
|
'noop fn': () => {
|
|
assertExists(noop);
|
|
assertEquals(noop(), undefined);
|
|
},
|
|
'highlightToColor()': () => {
|
|
[
|
|
HighlightType.Number,
|
|
HighlightType.Match,
|
|
HighlightType.String,
|
|
HighlightType.SingleLineComment,
|
|
HighlightType.MultiLineComment,
|
|
HighlightType.Keyword1,
|
|
HighlightType.Keyword2,
|
|
HighlightType.Operator,
|
|
HighlightType.None,
|
|
].forEach((type) => {
|
|
assertTrue(highlightToColor(type).length > 0);
|
|
});
|
|
},
|
|
'posSub()': () => {
|
|
assertEquals(posSub(14, 15), 0);
|
|
assertEquals(posSub(15, 1), 14);
|
|
},
|
|
'minSub()': () => {
|
|
assertEquals(minSub(13, 25, -1), -1);
|
|
assertEquals(minSub(25, 13, 0), 12);
|
|
},
|
|
'maxAdd()': () => {
|
|
assertEquals(maxAdd(99, 99, 75), 75);
|
|
assertEquals(maxAdd(25, 74, 101), 99);
|
|
},
|
|
'ord()': () => {
|
|
// Invalid output
|
|
assertEquals(ord(''), 256);
|
|
|
|
// Valid output
|
|
assertEquals(ord('a'), 97);
|
|
},
|
|
'strChars() properly splits strings into unicode characters': () => {
|
|
assertEquivalent(strChars('😺😸😹'), ['😺', '😸', '😹']);
|
|
},
|
|
'ctrlKey()': () => {
|
|
const ctrl_a = ctrlKey('a');
|
|
assertTrue(isControl(ctrl_a));
|
|
assertEquals(ctrl_a, String.fromCodePoint(0x01));
|
|
|
|
const invalid = ctrlKey('😺');
|
|
assertFalse(isControl(invalid));
|
|
assertEquals(invalid, '😺');
|
|
},
|
|
'isAscii()': () => {
|
|
assertTrue(isAscii('asjyverkjhsdf1928374'));
|
|
assertFalse(isAscii('😺acalskjsdf'));
|
|
assertFalse(isAscii('ab😺ac'));
|
|
},
|
|
'isAsciiDigit()': () => {
|
|
assertTrue(isAsciiDigit('1234567890'));
|
|
assertFalse(isAsciiDigit('A1'));
|
|
assertFalse(isAsciiDigit('/'));
|
|
assertFalse(isAsciiDigit(':'));
|
|
},
|
|
'isControl()': () => {
|
|
assertFalse(isControl('abc'));
|
|
assertTrue(isControl(String.fromCodePoint(0x01)));
|
|
assertFalse(isControl('😺'));
|
|
},
|
|
'strlen()': () => {
|
|
// Ascii length
|
|
assertEquals(strlen('abc'), 'abc'.length);
|
|
|
|
// Get number of visible unicode characters
|
|
assertEquals(strlen('😺😸😹'), 3);
|
|
assertNotEquals('😺😸😹'.length, strlen('😺😸😹'));
|
|
|
|
// Skin tone modifier + base character
|
|
assertEquals(strlen('🤰🏼'), 2);
|
|
assertNotEquals('🤰🏼'.length, strlen('🤰🏼'));
|
|
|
|
// This has 4 sub-characters, and 3 zero-width-joiners
|
|
assertEquals(strlen('👨👩👧👦'), 7);
|
|
assertNotEquals('👨👩👧👦'.length, strlen('👨👩👧👦'));
|
|
},
|
|
'truncate()': () => {
|
|
assertEquals(truncate('😺😸😹', 1), '😺');
|
|
assertEquals(truncate('😺😸😹', 5), '😺😸😹');
|
|
assertEquals(truncate('👨👩👧👦', 5), '👨👩👧');
|
|
},
|
|
};
|
|
};
|
|
|
|
const readKeyTest = () => {
|
|
const { KeyCommand } = _Ansi;
|
|
const { readKey, ctrlKey } = Fn;
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
const testKeyMap = (codes: string[], expected: string) => {
|
|
codes.forEach((code) => {
|
|
assertEquals(readKey(encoder.encode(code)), expected);
|
|
});
|
|
};
|
|
|
|
return {
|
|
'empty input': () => {
|
|
assertEquals(readKey(new Uint8Array(0)), '');
|
|
},
|
|
'passthrough': () => {
|
|
// Ignore unhandled escape sequences
|
|
assertEquals(readKey(encoder.encode('\x1b[]')), '\x1b[]');
|
|
|
|
// Pass explicitly mapped values right through
|
|
assertEquals(
|
|
readKey(encoder.encode(KeyCommand.ArrowUp)),
|
|
KeyCommand.ArrowUp,
|
|
);
|
|
assertEquals(
|
|
readKey(encoder.encode(KeyCommand.Home)),
|
|
KeyCommand.Home,
|
|
);
|
|
assertEquals(
|
|
readKey(encoder.encode(KeyCommand.Delete)),
|
|
KeyCommand.Delete,
|
|
);
|
|
|
|
// And pass through whatever else
|
|
assertEquals(readKey(encoder.encode('foobaz')), 'foobaz');
|
|
},
|
|
|
|
'Esc': () => testKeyMap(['\x1b', ctrlKey('l')], KeyCommand.Escape),
|
|
'Backspace': () =>
|
|
testKeyMap(
|
|
[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),
|
|
};
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Tests by module
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const ANSITest = () => {
|
|
const { Ground } = _Ansi;
|
|
|
|
return {
|
|
'color()': () => {
|
|
assertEquals(Ansi.color.Blue, '\x1b[34m');
|
|
},
|
|
'color256()': () => {
|
|
assertEquals(Ansi.color256(128, Ground.Back), '\x1b[48;5;128m');
|
|
assertEquals(Ansi.color256(128, Ground.Fore), '\x1b[38;5;128m');
|
|
},
|
|
'rgb()': () => {
|
|
assertEquals(Ansi.rgb(32, 64, 128, Ground.Back), '\x1b[48;2;32;64;128m');
|
|
assertEquals(Ansi.rgb(32, 64, 128, Ground.Fore), '\x1b[38;2;32;64;128m');
|
|
},
|
|
'moveCursor()': () => {
|
|
assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H');
|
|
},
|
|
'moveCursorForward()': () => {
|
|
assertEquals(Ansi.moveCursorForward(2), '\x1b[2C');
|
|
},
|
|
'moveCursorDown()': () => {
|
|
assertEquals(Ansi.moveCursorDown(7), '\x1b[7B');
|
|
},
|
|
};
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const BufferTest = {
|
|
'new Buffer': () => {
|
|
const b = Buffer.default();
|
|
assertInstanceOf(b, Buffer);
|
|
assertEquals(b.strlen(), 0);
|
|
},
|
|
'.appendLine': () => {
|
|
const b = Buffer.default();
|
|
|
|
// Carriage return and line feed
|
|
b.appendLine();
|
|
assertEquals(b.strlen(), 2);
|
|
|
|
b.clear();
|
|
assertEquals(b.strlen(), 0);
|
|
|
|
b.appendLine('foo');
|
|
assertEquals(b.strlen(), 5);
|
|
},
|
|
'.append': () => {
|
|
const b = Buffer.default();
|
|
|
|
b.append('foobar');
|
|
assertEquals(b.strlen(), 6);
|
|
b.clear();
|
|
|
|
b.append('foobar', 3);
|
|
assertEquals(b.strlen(), 3);
|
|
},
|
|
'.flush': async () => {
|
|
const b = Buffer.default();
|
|
b.appendLine('foobarbaz' + Ansi.ClearLine);
|
|
assertEquals(b.strlen(), 14);
|
|
|
|
await b.flush();
|
|
|
|
assertEquals(b.strlen(), 0);
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const DocumentTest = {
|
|
'.default': () => {
|
|
const doc = Document.default();
|
|
assertEquals(doc.numRows, 0);
|
|
assertTrue(doc.isEmpty());
|
|
assertEquivalent(doc.row(0), None);
|
|
},
|
|
'.open': async () => {
|
|
const oldDoc = Document.default();
|
|
oldDoc.insert(Position.default(), 'foobarbaz');
|
|
assertTrue(oldDoc.dirty);
|
|
assertEquals(oldDoc.numRows, 1);
|
|
|
|
const doc = await oldDoc.open(THIS_FILE);
|
|
assertEquals(FileLang.TypeScript, doc.fileType);
|
|
assertFalse(doc.dirty);
|
|
assertFalse(doc.isEmpty());
|
|
assertTrue(doc.numRows > 1);
|
|
},
|
|
'.save': async () => {
|
|
const doc = await Document.default().open(THIS_FILE);
|
|
doc.insertNewline(Position.default());
|
|
assertTrue(doc.dirty);
|
|
|
|
await doc.save('test.file');
|
|
|
|
fs.rm('test.file', (err: any) => {
|
|
assertNone(Option.from(err));
|
|
});
|
|
|
|
assertFalse(doc.dirty);
|
|
},
|
|
'.find': async () => {
|
|
const doc = await Document.default().open(KILO_FILE);
|
|
|
|
// First search forward from the beginning of the file
|
|
const query1 = doc.find(
|
|
'editor',
|
|
Position.default(),
|
|
SearchDirection.Forward,
|
|
);
|
|
assertTrue(query1.isSome());
|
|
const pos1 = query1.unwrap();
|
|
assertEquivalent(pos1, Position.at(5, 27));
|
|
|
|
// Now search backwards from line 400
|
|
const query2 = doc.find(
|
|
'realloc',
|
|
Position.at(44, 400),
|
|
SearchDirection.Backward,
|
|
);
|
|
assertTrue(query2.isSome());
|
|
const pos2 = query2.unwrap();
|
|
assertEquivalent(pos2, Position.at(11, 330));
|
|
|
|
// And backwards again
|
|
const query3 = doc.find(
|
|
'editor',
|
|
Position.from(pos2),
|
|
SearchDirection.Backward,
|
|
);
|
|
assertTrue(query3.isSome());
|
|
const pos3 = query3.unwrap();
|
|
assertEquivalent(pos3, Position.at(5, 328));
|
|
},
|
|
'.find - empty result': () => {
|
|
const doc = Document.default();
|
|
doc.insertNewline(Position.default());
|
|
|
|
const query = doc.find('foo', Position.default(), SearchDirection.Forward);
|
|
assertNone(query);
|
|
|
|
const query2 = doc.find('bar', Position.at(0, 5), SearchDirection.Forward);
|
|
assertNone(query2);
|
|
},
|
|
'.insert': () => {
|
|
const doc = Document.default();
|
|
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);
|
|
|
|
// Update row
|
|
doc.highlight(None, None);
|
|
|
|
const row0 = doc.row(0).unwrap();
|
|
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);
|
|
},
|
|
'.insertNewline': () => {
|
|
// Invalid insert location
|
|
const doc = Document.default();
|
|
doc.insertNewline(Position.at(0, 3));
|
|
assertFalse(doc.dirty);
|
|
assertTrue(doc.isEmpty());
|
|
|
|
// Add new empty row
|
|
const doc2 = Document.default();
|
|
doc2.insertNewline(Position.default());
|
|
assertTrue(doc2.dirty);
|
|
assertFalse(doc2.isEmpty());
|
|
|
|
// Split an existing line
|
|
const doc3 = Document.default();
|
|
doc3.insert(Position.default(), 'foobar');
|
|
doc3.insertNewline(Position.at(3, 0));
|
|
assertEquals(doc3.numRows, 2);
|
|
assertEquals(doc3.row(0).unwrap().toString(), 'foo');
|
|
assertEquals(doc3.row(1).unwrap().toString(), 'bar');
|
|
},
|
|
'.delete': () => {
|
|
const doc = Document.default();
|
|
doc.insert(Position.default(), 'foobar');
|
|
doc.delete(Position.at(3, 0));
|
|
assertEquals(doc.row(0).unwrap().toString(), 'fooar');
|
|
|
|
// Merge next row
|
|
const doc2 = Document.default();
|
|
doc2.insertNewline(Position.default());
|
|
doc2.insert(Position.at(0, 1), 'foobar');
|
|
doc2.delete(Position.at(0, 0));
|
|
assertEquals(doc2.row(0).unwrap().toString(), 'foobar');
|
|
|
|
// Invalid delete location
|
|
const doc3 = Document.default();
|
|
doc3.insert(Position.default(), 'foobar');
|
|
doc3.delete(Position.at(0, 3));
|
|
assertEquals(doc3.row(0).unwrap().toString(), 'foobar');
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const EditorTest = {
|
|
'new Editor': () => {
|
|
const e = Editor.create(defaultTerminalSize);
|
|
assertInstanceOf(e, Editor);
|
|
},
|
|
'.open': async () => {
|
|
const e = Editor.create(defaultTerminalSize);
|
|
await e.open(THIS_FILE);
|
|
assertInstanceOf(e, Editor);
|
|
},
|
|
'.processKeyPress - letters': async () => {
|
|
const e = Editor.create(defaultTerminalSize);
|
|
const res = await e.processKeyPress('a');
|
|
assertTrue(res);
|
|
},
|
|
'.processKeyPress - ctrl-q': async () => {
|
|
// Dirty file (Need to clear confirmation messages)
|
|
const e = Editor.create(defaultTerminalSize);
|
|
await e.processKeyPress('d');
|
|
assertTrue(await e.processKeyPress(Fn.ctrlKey('q')));
|
|
assertTrue(await e.processKeyPress(Fn.ctrlKey('q')));
|
|
assertTrue(await e.processKeyPress(Fn.ctrlKey('q')));
|
|
assertFalse(await e.processKeyPress(Fn.ctrlKey('q')));
|
|
|
|
// Clean file
|
|
const e2 = Editor.create(defaultTerminalSize);
|
|
const res = await e2.processKeyPress(Fn.ctrlKey('q'));
|
|
assertFalse(res);
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const FileTypeTest = {
|
|
'FileType.from()': () => {
|
|
for (const [ext, typeClass] of FT.fileTypeMap.entries()) {
|
|
const file = `test${ext}`;
|
|
const syntax = FileType.from(file);
|
|
|
|
assertInstanceOf(syntax, typeClass);
|
|
}
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const OptionTest = {
|
|
'Option.from()': () => {
|
|
assertNone(Option.from(null));
|
|
assertNone(Option.from());
|
|
assertEquivalent(Option.from(undefined), None);
|
|
|
|
assertSome(Option.from('foo'));
|
|
assertSome(Option.from(234));
|
|
assertSome(Option.from({}));
|
|
assertSome(Some([1, 2, 3]));
|
|
|
|
assertEquivalent(Option.from(Some('foo')), Some('foo'));
|
|
assertEquivalent(Some(Some('bar')), Some('bar'));
|
|
},
|
|
'.isSome': () => {
|
|
assertFalse(None.isSome());
|
|
assertTrue(Option.from('foo').isSome());
|
|
assertTrue(Some('foo').isSome());
|
|
},
|
|
'.isNone': () => {
|
|
assertTrue(None.isNone());
|
|
assertFalse(Option.from('foo').isNone());
|
|
assertFalse(Some('foo').isNone());
|
|
},
|
|
'.toString': () => {
|
|
assertEquals(Some({}).toString(), 'Some ({})');
|
|
assertEquals(Some([1, 2, 3]).toString(), 'Some ([1,2,3])');
|
|
assertEquals(None.toString(), 'None');
|
|
},
|
|
'.isSomeAnd': () => {
|
|
assertFalse(Option.from().isSomeAnd((_a) => true));
|
|
assertTrue(Option.from('foo').isSomeAnd((a) => typeof a === 'string'));
|
|
},
|
|
'.isNoneAnd': () => {
|
|
assertTrue(None.isNoneAnd(() => true));
|
|
assertFalse(None.isNoneAnd(() => false));
|
|
assertFalse(Some('x').isNoneAnd(() => true));
|
|
},
|
|
'.map': () => {
|
|
const fn = (_a: any) => 'bar';
|
|
|
|
assertEquivalent(Some('bar'), Some('foo').map(fn));
|
|
assertNone(None.map(fn));
|
|
},
|
|
'.mapOr': () => {
|
|
const fn = (_a: any) => 'bar';
|
|
|
|
assertEquals('bar', Some('foo').mapOr('baz', fn));
|
|
assertEquals('baz', None.mapOr('baz', fn));
|
|
},
|
|
'.mapOrElse': () => {
|
|
const fn = (_a: any) => 'bar';
|
|
const defFn = () => 'baz';
|
|
|
|
assertEquals('bar', Some('foo').mapOrElse(defFn, fn));
|
|
assertEquals('baz', None.mapOrElse(defFn, fn));
|
|
},
|
|
'.unwrapOr': () => {
|
|
assertEquals('foo', Some('foo').unwrapOr('bar'));
|
|
assertEquals('bar', None.unwrapOr('bar'));
|
|
},
|
|
'.unwrapOrElse': () => {
|
|
const fn = () => 'bar';
|
|
assertEquals('foo', Some('foo').unwrapOrElse(fn));
|
|
assertEquals('bar', None.unwrapOrElse(fn));
|
|
},
|
|
'.and': () => {
|
|
const optb = Some('bar');
|
|
assertEquivalent(optb, Some('foo').and(optb));
|
|
assertEquivalent(None, None.and(optb));
|
|
},
|
|
'.andThen': () => {
|
|
const fn = (x: any) => Some(typeof x === 'string');
|
|
assertEquivalent(Some(true), Some('foo').andThen(fn));
|
|
assertNone(None.andThen(fn));
|
|
},
|
|
'.or': () => {
|
|
const optb = Some('bar');
|
|
assertEquivalent(Some('foo'), Some('foo').or(optb));
|
|
assertEquivalent(optb, None.or(optb));
|
|
},
|
|
'.orElse': () => {
|
|
const fn = () => Some('bar');
|
|
assertEquivalent(Some('foo'), Some('foo').orElse(fn));
|
|
assertEquivalent(Some('bar'), None.orElse(fn));
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const PositionTest = {
|
|
'.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);
|
|
},
|
|
'.from': () => {
|
|
const p1 = Position.at(1, 2);
|
|
const p2 = Position.from(p1);
|
|
|
|
p1.x = 2;
|
|
p1.y = 4;
|
|
|
|
assertEquals(p1.x, 2);
|
|
assertEquals(p1.y, 4);
|
|
|
|
assertEquals(p2.x, 1);
|
|
assertEquals(p2.y, 2);
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
const RowTest = {
|
|
'.default': () => {
|
|
const row = Row.default();
|
|
assertEquals(row.toString(), '');
|
|
},
|
|
'.from': () => {
|
|
// 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(), '😺😸😹');
|
|
},
|
|
'.append': () => {
|
|
const row = Row.from('foo');
|
|
row.append('bar', FileType.default());
|
|
assertEquals(row.toString(), 'foobar');
|
|
},
|
|
'.delete': () => {
|
|
const row = Row.from('foof');
|
|
row.delete(3);
|
|
assertEquals(row.toString(), 'foo');
|
|
|
|
row.delete(4);
|
|
assertEquals(row.toString(), 'foo');
|
|
},
|
|
'.split': () => {
|
|
// When you split a row, it's from the cursor position
|
|
// (Kind of like if the string were one-indexed)
|
|
const row = Row.from('foobar');
|
|
const row2 = Row.from('bar');
|
|
assertEquals(row.split(3, FileType.default()).toString(), row2.toString());
|
|
},
|
|
'.find': () => {
|
|
const normalRow = Row.from('\tFor whom the bell tolls');
|
|
assertEquivalent(
|
|
normalRow.find('who', 0, SearchDirection.Forward),
|
|
Some(5),
|
|
);
|
|
assertEquals(normalRow.find('foo', 0, SearchDirection.Forward), None);
|
|
|
|
const emojiRow = Row.from('\t😺😸😹');
|
|
assertEquivalent(emojiRow.find('😹', 0, SearchDirection.Forward), Some(3));
|
|
assertEquals(emojiRow.find('🤰🏼', 10, SearchDirection.Forward), None);
|
|
},
|
|
'.find backwards': () => {
|
|
const normalRow = Row.from('For whom the bell tolls');
|
|
assertEquivalent(
|
|
normalRow.find('who', 23, SearchDirection.Backward),
|
|
Some(4),
|
|
);
|
|
assertEquals(normalRow.find('foo', 10, SearchDirection.Backward), None);
|
|
|
|
const emojiRow = Row.from('😺😸😹');
|
|
assertEquivalent(emojiRow.find('😸', 2, SearchDirection.Backward), Some(1));
|
|
assertEquals(emojiRow.find('🤰🏼', 10, SearchDirection.Backward), None);
|
|
},
|
|
'.byteIndexToCharIndex': () => {
|
|
// Each 'character' is two bytes
|
|
const row = Row.from('😺😸😹👨👩👧👦');
|
|
assertEquals(row.byteIndexToCharIndex(4), 2);
|
|
assertEquals(row.byteIndexToCharIndex(2), 1);
|
|
assertEquals(row.byteIndexToCharIndex(0), 0);
|
|
|
|
// Return count on nonsense index
|
|
assertEquals(Fn.strlen(row.toString()), 10);
|
|
assertEquals(row.byteIndexToCharIndex(72), 10);
|
|
|
|
const row2 = Row.from('foobar');
|
|
assertEquals(row2.byteIndexToCharIndex(2), 2);
|
|
},
|
|
'.charIndexToByteIndex': () => {
|
|
// Each 'character' is two bytes
|
|
const row = Row.from('😺😸😹👨👩👧👦');
|
|
assertEquals(row.charIndexToByteIndex(2), 4);
|
|
assertEquals(row.charIndexToByteIndex(1), 2);
|
|
assertEquals(row.charIndexToByteIndex(0), 0);
|
|
},
|
|
'.cxToRx, .rxToCx': () => {
|
|
const row = Row.from('foo\tbar\tbaz');
|
|
row.update(None, FileType.default());
|
|
assertNotEquals(row.chars, row.rchars);
|
|
assertNotEquals(row.size, row.rsize);
|
|
assertEquals(row.size, 11);
|
|
assertEquals(row.rsize, row.size + (SCROLL_TAB_SIZE * 2) - 2);
|
|
|
|
const cx = 11;
|
|
const aRx = row.cxToRx(cx);
|
|
const rx = 11;
|
|
const aCx = row.rxToCx(aRx);
|
|
assertEquals(aCx, cx);
|
|
assertEquals(aRx, rx);
|
|
},
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Test Suite Setup
|
|
// ----------------------------------------------------------------------------
|
|
|
|
testSuite({
|
|
fns: fnTest(),
|
|
'readKey()': readKeyTest(),
|
|
'ANSI utils': ANSITest(),
|
|
Buffer: BufferTest,
|
|
Document: DocumentTest,
|
|
Editor: EditorTest,
|
|
FileType: FileTypeTest,
|
|
Option: OptionTest,
|
|
Position: PositionTest,
|
|
Row: RowTest,
|
|
});
|