Add util functions for null/undefined checks, organize tests into sections
Some checks failed
timw4mail/scroll/pipeline/head There was a failure building this commit

This commit is contained in:
Timothy Warren 2024-06-26 13:40:42 -04:00
parent e6b53ef327
commit 8093683f92
4 changed files with 440 additions and 360 deletions

View File

@ -19,15 +19,7 @@ const {
testSuite, testSuite,
} = await getTestRunner(); } = await getTestRunner();
const encoder = new TextEncoder(); const ANSITest = {
const testKeyMap = (codes: string[], expected: string) => {
codes.forEach((code) => {
assertEquals(Fn.readKey(encoder.encode(code)), expected);
});
};
testSuite({
'ANSI utils': {
'moveCursor()': () => { 'moveCursor()': () => {
assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H'); assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H');
}, },
@ -37,8 +29,11 @@ testSuite({
'moveCursorDown()': () => { 'moveCursorDown()': () => {
assertEquals(Ansi.moveCursorDown(7), '\x1b[7B'); assertEquals(Ansi.moveCursorDown(7), '\x1b[7B');
}, },
}, };
Buffer: {
// ----------------------------------------------------------------------------
const BufferTest = {
'new Buffer': () => { 'new Buffer': () => {
const b = new Buffer(); const b = new Buffer();
assertInstanceOf(b, Buffer); assertInstanceOf(b, Buffer);
@ -76,8 +71,11 @@ testSuite({
assertEquals(b.strlen(), 0); assertEquals(b.strlen(), 0);
}, },
}, };
Document: {
// ----------------------------------------------------------------------------
const DocumentTest = {
'.default': () => { '.default': () => {
const doc = Document.default(); const doc = Document.default();
assertEquals(doc.numRows, 0); assertEquals(doc.numRows, 0);
@ -120,14 +118,20 @@ testSuite({
doc.delete(Position.at(3, 0)); doc.delete(Position.at(3, 0));
assertEquals(doc.row(0)?.toString(), 'fooar'); assertEquals(doc.row(0)?.toString(), 'fooar');
}, },
}, };
Editor: {
// ----------------------------------------------------------------------------
const EditorTest = {
'new Editor': () => { 'new Editor': () => {
const e = new Editor(defaultTerminalSize); const e = new Editor(defaultTerminalSize);
assertInstanceOf(e, Editor); assertInstanceOf(e, Editor);
}, },
}, };
Position: {
// ----------------------------------------------------------------------------
const PositionTest = {
'.default': () => { '.default': () => {
const p = Position.default(); const p = Position.default();
assertEquals(p.x, 0); assertEquals(p.x, 0);
@ -151,8 +155,11 @@ testSuite({
assertEquals(p2.x, 1); assertEquals(p2.x, 1);
assertEquals(p2.y, 2); assertEquals(p2.y, 2);
}, },
}, };
Row: {
// ----------------------------------------------------------------------------
const RowTest = {
'.default': () => { '.default': () => {
const row = Row.default(); const row = Row.default();
assertEquals(row.toString(), ''); assertEquals(row.toString(), '');
@ -233,8 +240,28 @@ testSuite({
assertEquals(aCx, cx); assertEquals(aCx, cx);
assertEquals(aRx, rx); assertEquals(aRx, rx);
}, },
};
// ----------------------------------------------------------------------------
const fnTest = {
'defined()': () => {
const { defined } = Fn;
assertFalse(defined(null));
assertFalse(defined(void 0));
assertFalse(defined(undefined));
assertTrue(defined(0));
assertTrue(defined(false));
},
'nullish()': () => {
const { nullish } = Fn;
assertTrue(nullish(null));
assertTrue(nullish(void 0));
assertTrue(nullish(undefined));
assertFalse(nullish(0));
assertFalse(nullish(false));
}, },
'fns': {
'arrayInsert() strings': () => { 'arrayInsert() strings': () => {
const { arrayInsert } = Fn; const { arrayInsert } = Fn;
@ -347,8 +374,19 @@ testSuite({
assertEquals(truncate('😺😸😹', 5), '😺😸😹'); assertEquals(truncate('😺😸😹', 5), '😺😸😹');
assertEquals(truncate('👨‍👩‍👧‍👦', 5), '👨‍👩‍👧'); assertEquals(truncate('👨‍👩‍👧‍👦', 5), '👨‍👩‍👧');
}, },
}, };
'readKey()': {
// ----------------------------------------------------------------------------
const encoder = new TextEncoder();
const testKeyMap = (codes: string[], expected: string) => {
codes.forEach((code) => {
assertEquals(Fn.readKey(encoder.encode(code)), expected);
});
};
const readKeyTest = {
'empty input': () => { 'empty input': () => {
assertEquals(Fn.readKey(new Uint8Array(0)), ''); assertEquals(Fn.readKey(new Uint8Array(0)), '');
}, },
@ -385,5 +423,19 @@ testSuite({
'End': () => 'End': () =>
testKeyMap(['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'], KeyCommand.End), testKeyMap(['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'], KeyCommand.End),
'Enter': () => testKeyMap(['\n', '\r', '\v'], KeyCommand.Enter), 'Enter': () => testKeyMap(['\n', '\r', '\v'], KeyCommand.Enter),
}, };
// ----------------------------------------------------------------------------
// Test Suite Setup
// ----------------------------------------------------------------------------
testSuite({
'ANSI utils': ANSITest,
Buffer: BufferTest,
Document: DocumentTest,
Editor: EditorTest,
Position: PositionTest,
Row: RowTest,
fns: fnTest,
'readKey()': readKeyTest,
}); });

View File

@ -81,7 +81,7 @@ export class Document {
const row = this.#rows[potential.y]; const row = this.#rows[potential.y];
// Okay, we have to take the Javascript string index (potential.x), convert // Okay, we have to take the Javascript string index (potential.x), convert
// it to the Row character index, and then convert that to the Row render index // it to the Row 'character' index, and then convert that to the Row render index
// so that the highlighted color starts in the right place. // so that the highlighted color starts in the right place.
const start = row.cxToRx(row.byteIndexToCharIndex(potential.x)); const start = row.cxToRx(row.byteIndexToCharIndex(potential.x));

View File

@ -11,6 +11,20 @@ const decoder = new TextDecoder();
*/ */
export const noop = () => {}; export const noop = () => {};
/**
* Does a value exist? (not null or undefined)
*/
export function defined(v: unknown): boolean {
return v !== null && typeof v !== 'undefined';
}
/**
* Is the value null or undefined?
*/
export function nullish(v: unknown): boolean {
return v === null || typeof v === 'undefined';
}
/** /**
* Convert input from ANSI escape sequences into a form * Convert input from ANSI escape sequences into a form
* that can be more easily mapped to editor commands * that can be more easily mapped to editor commands

View File

@ -107,6 +107,9 @@ export class Row {
return this.byteIndexToCharIndex(byteCount); return this.byteIndexToCharIndex(byteCount);
} }
/**
* Convert the raw row offset to the equivalent offset for screen rendering
*/
public cxToRx(cx: number): number { public cxToRx(cx: number): number {
let rx = 0; let rx = 0;
let j; let j;
@ -120,6 +123,9 @@ export class Row {
return rx; return rx;
} }
/**
* Convert the screen rendering row offset to the file row offset
*/
public rxToCx(rx: number): number { public rxToCx(rx: number): number {
let curRx = 0; let curRx = 0;
let cx = 0; let cx = 0;
@ -137,6 +143,10 @@ export class Row {
return cx; return cx;
} }
/**
* Convert the index of a JS string into the equivalent
* 'unicode character' index
*/
public byteIndexToCharIndex(byteIndex: number): number { public byteIndexToCharIndex(byteIndex: number): number {
if (this.toString().length === this.chars.length) { if (this.toString().length === this.chars.length) {
return byteIndex; return byteIndex;
@ -154,6 +164,10 @@ export class Row {
return this.chars.length; return this.chars.length;
} }
/**
* Convert the 'unicode character' index into the equivalent
* JS string index
*/
public charIndexToByteIndex(charIndex: number): number { public charIndexToByteIndex(charIndex: number): number {
if (charIndex === 0 || this.toString().length === this.chars.length) { if (charIndex === 0 || this.toString().length === this.chars.length) {
return charIndex; return charIndex;