Refactor tests to be consistent for both runtimes
This commit is contained in:
parent
8b5fb17603
commit
2aaf1c678b
12
bunfig.toml
Normal file
12
bunfig.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
logLevel = "debug" # "debug" | "warn" | "error"
|
||||||
|
telemetry = false
|
||||||
|
|
||||||
|
[install.lockfile]
|
||||||
|
save = false
|
||||||
|
|
||||||
|
[test]
|
||||||
|
# always enable coverage
|
||||||
|
coverage = true
|
||||||
|
|
||||||
|
# disable code coverage counting test files
|
||||||
|
coverageSkipTestFiles = true
|
@ -3,14 +3,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IRuntime, RunTimeType } from '../common/mod.ts';
|
import { IRuntime, RunTimeType } from '../common/mod.ts';
|
||||||
import BunFFI from './ffi.ts';
|
|
||||||
import BunTerminalIO from './terminal_io.ts';
|
import BunTerminalIO from './terminal_io.ts';
|
||||||
import BunFileIO from './file_io.ts';
|
import BunFileIO from './file_io.ts';
|
||||||
|
|
||||||
const BunRuntime: IRuntime = {
|
const BunRuntime: IRuntime = {
|
||||||
name: RunTimeType.Bun,
|
name: RunTimeType.Bun,
|
||||||
file: BunFileIO,
|
file: BunFileIO,
|
||||||
ffi: BunFFI,
|
|
||||||
term: BunTerminalIO,
|
term: BunTerminalIO,
|
||||||
onEvent: (eventName: string, handler) => process.on(eventName, handler),
|
onEvent: (eventName: string, handler) => process.on(eventName, handler),
|
||||||
onExit: (cb: () => void): void => {
|
onExit: (cb: () => void): void => {
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Wrap the runtime-specific hook into stdin
|
* Wrap the runtime-specific hook into stdin
|
||||||
*/
|
*/
|
||||||
import { ITerminal, ITerminalSize } from '../common/mod.ts';
|
import {
|
||||||
|
defaultTerminalSize,
|
||||||
|
ITerminal,
|
||||||
|
ITerminalSize,
|
||||||
|
} from '../common/mod.ts';
|
||||||
import Ansi from '../common/ansi.ts';
|
import Ansi from '../common/ansi.ts';
|
||||||
|
|
||||||
const defaultTerminalSize: ITerminalSize = {
|
|
||||||
rows: 24,
|
|
||||||
cols: 80,
|
|
||||||
};
|
|
||||||
|
|
||||||
const BunTerminalIO: ITerminal = {
|
const BunTerminalIO: ITerminal = {
|
||||||
// Deno only returns arguments passed to the script, so
|
// Deno only returns arguments passed to the script, so
|
||||||
// remove the bun runtime executable, and entry script arguments
|
// remove the bun runtime executable, and entry script arguments
|
||||||
|
@ -1,42 +1,35 @@
|
|||||||
/**
|
/**
|
||||||
* Adapt the bun test interface to the shared testing interface
|
* Adapt the bun test interface to the shared testing interface
|
||||||
*/
|
*/
|
||||||
import { expect, test as btest } from 'bun:test';
|
import { describe, expect, test } from 'bun:test';
|
||||||
import { ITestBase } from '../common/mod.ts';
|
import { ITestBase } from '../common/mod.ts';
|
||||||
|
|
||||||
class TestBase implements ITestBase {
|
export function testSuite(testObj: any) {
|
||||||
test(name: string, fn: () => void) {
|
Object.keys(testObj).forEach((group) => {
|
||||||
return btest(name, fn);
|
describe(group, () => {
|
||||||
}
|
const groupObj = testObj[group];
|
||||||
|
Object.keys(groupObj).forEach((testName) => {
|
||||||
assertEquals(actual: unknown, expected: unknown): void {
|
test(testName, groupObj[testName]);
|
||||||
return expect(actual).toEqual(expected);
|
});
|
||||||
}
|
});
|
||||||
|
});
|
||||||
assertExists(actual: unknown): void {
|
|
||||||
return expect(actual).toBeDefined();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertInstanceOf(actual: unknown, expectedType: any): void {
|
|
||||||
return expect(actual).toBeInstanceOf(expectedType);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNotEquals(actual: unknown, expected: unknown): void {
|
|
||||||
return expect(actual).not.toBe(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertFalse(actual: boolean): void {
|
|
||||||
return expect(actual).toBe(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(actual: boolean): void {
|
|
||||||
return expect(actual).toBe(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertStrictEquals(actual: unknown, expected: unknown): void {
|
|
||||||
return expect(actual).toBe(expected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testBase = new TestBase();
|
const BunTestBase: ITestBase = {
|
||||||
export default testBase;
|
assertEquals: (actual: unknown, expected: unknown) =>
|
||||||
|
expect(actual).toEqual(expected),
|
||||||
|
assertExists: (actual: unknown) => expect(actual).toBeDefined(),
|
||||||
|
assertFalse: (actual: boolean) => expect(actual).toBe(false),
|
||||||
|
assertInstanceOf: (actual: unknown, expectedType: any) =>
|
||||||
|
expect(actual).toBeInstanceOf(expectedType),
|
||||||
|
assertNotEquals: (actual: unknown, expected: unknown) =>
|
||||||
|
expect(actual).not.toBe(expected),
|
||||||
|
assertStrictEquals: (actual: unknown, expected: unknown) =>
|
||||||
|
expect(actual).toBe(expected),
|
||||||
|
assertTrue: (actual: boolean) => expect(actual).toBe(true),
|
||||||
|
test,
|
||||||
|
testGroup: describe,
|
||||||
|
testSuite,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BunTestBase;
|
||||||
|
177
src/common/all_test.ts
Normal file
177
src/common/all_test.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import { getTestRunner } from './runtime.ts';
|
||||||
|
import { Ansi, KeyCommand, readKey } from './ansi.ts';
|
||||||
|
import { Document, Row } from './document.ts';
|
||||||
|
import Buffer from './buffer.ts';
|
||||||
|
import {
|
||||||
|
chars,
|
||||||
|
ctrl_key,
|
||||||
|
is_ascii,
|
||||||
|
is_control,
|
||||||
|
noop,
|
||||||
|
ord,
|
||||||
|
strlen,
|
||||||
|
truncate,
|
||||||
|
} from './utils.ts';
|
||||||
|
import { Editor } from './editor.ts';
|
||||||
|
import { defaultTerminalSize } from './termios.ts';
|
||||||
|
|
||||||
|
const t = await getTestRunner();
|
||||||
|
|
||||||
|
const testObj = {
|
||||||
|
'ANSI::ANSI utils': {
|
||||||
|
'Ansi.moveCursor': () => {
|
||||||
|
t.assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H');
|
||||||
|
},
|
||||||
|
'Ansi.moveCursorForward': () => {
|
||||||
|
t.assertEquals(Ansi.moveCursorForward(2), '\x1b[2C');
|
||||||
|
},
|
||||||
|
'Ansi.moveCursorDown': () => {
|
||||||
|
t.assertEquals(Ansi.moveCursorDown(7), '\x1b[7B');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'ANSI::readKey()': {
|
||||||
|
'readKey passthrough': () => {
|
||||||
|
// Ignore unhandled escape sequences
|
||||||
|
t.assertEquals(readKey('\x1b[]'), '\x1b[]');
|
||||||
|
|
||||||
|
// Pass explicitly mapped values right through
|
||||||
|
t.assertEquals(readKey(KeyCommand.ArrowUp), KeyCommand.ArrowUp);
|
||||||
|
t.assertEquals(readKey(KeyCommand.Home), KeyCommand.Home);
|
||||||
|
t.assertEquals(readKey(KeyCommand.Delete), KeyCommand.Delete);
|
||||||
|
},
|
||||||
|
'readKey Home': () => {
|
||||||
|
['\x1b[1~', '\x1b[7~', '\x1b[H', '\x1bOH'].forEach((code) => {
|
||||||
|
t.assertEquals(readKey(code), KeyCommand.Home);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'readKey End': () => {
|
||||||
|
['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'].forEach((code) => {
|
||||||
|
t.assertEquals(readKey(code), KeyCommand.End);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Buffer: {
|
||||||
|
'Buffer exists': () => {
|
||||||
|
const b = new Buffer();
|
||||||
|
t.assertInstanceOf(b, Buffer);
|
||||||
|
t.assertEquals(b.strlen(), 0);
|
||||||
|
},
|
||||||
|
'Buffer.appendLine': () => {
|
||||||
|
const b = new Buffer();
|
||||||
|
|
||||||
|
// Carriage return and line feed
|
||||||
|
b.appendLine();
|
||||||
|
t.assertEquals(b.strlen(), 2);
|
||||||
|
|
||||||
|
b.clear();
|
||||||
|
t.assertEquals(b.strlen(), 0);
|
||||||
|
|
||||||
|
b.appendLine('foo');
|
||||||
|
t.assertEquals(b.strlen(), 5);
|
||||||
|
},
|
||||||
|
'Buffer.append': () => {
|
||||||
|
const b = new Buffer();
|
||||||
|
|
||||||
|
b.append('foobar');
|
||||||
|
t.assertEquals(b.strlen(), 6);
|
||||||
|
b.clear();
|
||||||
|
|
||||||
|
b.append('foobar', 3);
|
||||||
|
t.assertEquals(b.strlen(), 3);
|
||||||
|
},
|
||||||
|
'Buffer.flush': async () => {
|
||||||
|
const b = new Buffer();
|
||||||
|
b.appendLine('foobarbaz' + Ansi.ClearLine);
|
||||||
|
t.assertEquals(b.strlen(), 14);
|
||||||
|
|
||||||
|
await b.flush();
|
||||||
|
|
||||||
|
t.assertEquals(b.strlen(), 0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Document: {
|
||||||
|
'Document.empty': () => {
|
||||||
|
const doc = Document.empty();
|
||||||
|
t.assertEquals(doc.numRows, 0);
|
||||||
|
t.assertTrue(doc.isEmpty());
|
||||||
|
t.assertEquals(doc.row(0), null);
|
||||||
|
},
|
||||||
|
'Document.appendRow': () => {
|
||||||
|
const doc = Document.empty();
|
||||||
|
doc.appendRow('foobar');
|
||||||
|
t.assertEquals(doc.numRows, 1);
|
||||||
|
t.assertFalse(doc.isEmpty());
|
||||||
|
t.assertInstanceOf(doc.row(0), Row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Document::Row': {
|
||||||
|
'new Row': () => {
|
||||||
|
const row = new Row();
|
||||||
|
t.assertEquals(row.toString(), '');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Editor: {
|
||||||
|
'new Editor': () => {
|
||||||
|
const e = new Editor(defaultTerminalSize);
|
||||||
|
t.assertInstanceOf(e, Editor);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Util::Misc fns': {
|
||||||
|
'noop fn': () => {
|
||||||
|
t.assertExists(noop);
|
||||||
|
t.assertEquals(noop(), undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Util::String fns': {
|
||||||
|
'ord() returns 256 on invalid string': () => {
|
||||||
|
t.assertEquals(ord(''), 256);
|
||||||
|
},
|
||||||
|
'ord() returns number on valid string': () => {
|
||||||
|
t.assertEquals(ord('a'), 97);
|
||||||
|
},
|
||||||
|
'chars() properly splits strings into unicode characters': () => {
|
||||||
|
t.assertEquals(chars('😺😸😹'), ['😺', '😸', '😹']);
|
||||||
|
},
|
||||||
|
'ctrl_key() returns expected values': () => {
|
||||||
|
const ctrl_a = ctrl_key('a');
|
||||||
|
t.assertTrue(is_control(ctrl_a));
|
||||||
|
t.assertEquals(ctrl_a, String.fromCodePoint(0x01));
|
||||||
|
|
||||||
|
const invalid = ctrl_key('😺');
|
||||||
|
t.assertFalse(is_control(invalid));
|
||||||
|
t.assertEquals(invalid, '😺');
|
||||||
|
},
|
||||||
|
'is_ascii() properly discerns ascii chars': () => {
|
||||||
|
t.assertTrue(is_ascii('asjyverkjhsdf1928374'));
|
||||||
|
t.assertFalse(is_ascii('😺acalskjsdf'));
|
||||||
|
t.assertFalse(is_ascii('ab😺ac'));
|
||||||
|
},
|
||||||
|
'is_control() works as expected': () => {
|
||||||
|
t.assertFalse(is_control('abc'));
|
||||||
|
t.assertTrue(is_control(String.fromCodePoint(0x01)));
|
||||||
|
t.assertFalse(is_control('😺'));
|
||||||
|
},
|
||||||
|
'strlen() returns expected length for ascii strings': () => {
|
||||||
|
t.assertEquals(strlen('abc'), 'abc'.length);
|
||||||
|
},
|
||||||
|
'strlen() returns expected length for multibyte characters': () => {
|
||||||
|
t.assertEquals(strlen('😺😸😹'), 3);
|
||||||
|
t.assertNotEquals('😺😸😹'.length, strlen('😺😸😹'));
|
||||||
|
|
||||||
|
// Skin tone modifier + base character
|
||||||
|
t.assertEquals(strlen('🤰🏼'), 2);
|
||||||
|
t.assertNotEquals('🤰🏼'.length, strlen('🤰🏼'));
|
||||||
|
|
||||||
|
// This has 4 sub-characters, and 3 zero-width-joiners
|
||||||
|
t.assertEquals(strlen('👨👩👧👦'), 7);
|
||||||
|
t.assertNotEquals('👨👩👧👦'.length, strlen('👨👩👧👦'));
|
||||||
|
},
|
||||||
|
'truncate() shortens strings': () => {
|
||||||
|
t.assertEquals(truncate('😺😸😹', 1), '😺');
|
||||||
|
t.assertEquals(truncate('😺😸😹', 5), '😺😸😹');
|
||||||
|
t.assertEquals(truncate('👨👩👧👦', 5), '👨👩👧');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
t.testSuite(testObj);
|
@ -4,10 +4,6 @@
|
|||||||
|
|
||||||
export const ANSI_PREFIX = '\x1b[';
|
export const ANSI_PREFIX = '\x1b[';
|
||||||
|
|
||||||
function esc(pieces: TemplateStringsArray): string {
|
|
||||||
return ANSI_PREFIX + pieces[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ANSI escapes for various inputs
|
* ANSI escapes for various inputs
|
||||||
*/
|
*/
|
||||||
@ -26,12 +22,12 @@ export enum KeyCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Ansi = {
|
export const Ansi = {
|
||||||
ClearLine: esc`K`,
|
ClearLine: ANSI_PREFIX + 'K',
|
||||||
ClearScreen: esc`2J`,
|
ClearScreen: ANSI_PREFIX + '2J',
|
||||||
ResetCursor: esc`H`,
|
ResetCursor: ANSI_PREFIX + 'H',
|
||||||
HideCursor: esc`?25l`,
|
HideCursor: ANSI_PREFIX + '?25l',
|
||||||
ShowCursor: esc`?25h`,
|
ShowCursor: ANSI_PREFIX + '?25h',
|
||||||
GetCursorLocation: esc`6n`,
|
GetCursorLocation: ANSI_PREFIX + '6n',
|
||||||
moveCursor: function moveCursor(row: number, col: number): string {
|
moveCursor: function moveCursor(row: number, col: number): string {
|
||||||
// Convert to 1-based counting
|
// Convert to 1-based counting
|
||||||
row++;
|
row++;
|
||||||
@ -57,15 +53,15 @@ export function readKey(parsed: string): string {
|
|||||||
|
|
||||||
// Some keycodes have multiple potential inputs
|
// Some keycodes have multiple potential inputs
|
||||||
switch (parsed) {
|
switch (parsed) {
|
||||||
case '\x1bOH':
|
|
||||||
case '\x1b[7~':
|
|
||||||
case '\x1b[1~':
|
case '\x1b[1~':
|
||||||
|
case '\x1b[7~':
|
||||||
|
case '\x1bOH':
|
||||||
case '\x1b[H':
|
case '\x1b[H':
|
||||||
return KeyCommand.Home;
|
return KeyCommand.Home;
|
||||||
|
|
||||||
case '\x1bOF':
|
|
||||||
case '\x1b[8~':
|
|
||||||
case '\x1b[4~':
|
case '\x1b[4~':
|
||||||
|
case '\x1b[8~':
|
||||||
|
case '\x1bOF':
|
||||||
case '\x1b[F':
|
case '\x1b[F':
|
||||||
return KeyCommand.End;
|
return KeyCommand.End;
|
||||||
|
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import { getTestRunner } from './mod.ts';
|
|
||||||
import Ansi, { KeyCommand, readKey } from './ansi.ts';
|
|
||||||
|
|
||||||
getTestRunner().then((t) => {
|
|
||||||
t.test('Ansi.moveCursor', () => {
|
|
||||||
t.assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H');
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('Ansi.moveCursorForward', () => {
|
|
||||||
t.assertEquals(Ansi.moveCursorForward(2), '\x1b[2C');
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('Ansi.moveCursorDown', () => {
|
|
||||||
t.assertEquals(Ansi.moveCursorDown(7), '\x1b[7B');
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('readKey', () => {
|
|
||||||
// Ignore unhandled escape sequences
|
|
||||||
t.assertEquals(readKey('\x1b[]'), '\x1b[]');
|
|
||||||
|
|
||||||
// Pass explicitly mapped values right through
|
|
||||||
t.assertEquals(readKey(KeyCommand.ArrowUp), KeyCommand.ArrowUp);
|
|
||||||
t.assertEquals(readKey(KeyCommand.Home), KeyCommand.Home);
|
|
||||||
|
|
||||||
['\x1bOH', '\x1b[7~', '\x1b[1~', '\x1b[H'].forEach((code) => {
|
|
||||||
t.assertEquals(readKey(code), KeyCommand.Home);
|
|
||||||
});
|
|
||||||
|
|
||||||
['\x1bOF', '\x1b[8~', '\x1b[4~', '\x1b[F'].forEach((code) => {
|
|
||||||
t.assertEquals(readKey(code), KeyCommand.End);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,5 +1,5 @@
|
|||||||
import { strlen, truncate } from './utils.ts';
|
import { strlen, truncate } from './utils.ts';
|
||||||
import { getRuntime, importForRuntime } from './runtime.ts';
|
import { getRuntime } from './runtime.ts';
|
||||||
|
|
||||||
class Buffer {
|
class Buffer {
|
||||||
#b = '';
|
#b = '';
|
||||||
@ -23,7 +23,7 @@ class Buffer {
|
|||||||
* Output the contents of the buffer into stdout
|
* Output the contents of the buffer into stdout
|
||||||
*/
|
*/
|
||||||
public async flush() {
|
public async flush() {
|
||||||
const term = await importForRuntime('terminal_io');
|
const { term } = await getRuntime();
|
||||||
await term.writeStdout(this.#b);
|
await term.writeStdout(this.#b);
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { getTestRunner } from './runtime.ts';
|
|
||||||
import Buffer from './buffer.ts';
|
|
||||||
|
|
||||||
getTestRunner().then((t) => {
|
|
||||||
t.test('Buffer exists', () => {
|
|
||||||
const b = new Buffer();
|
|
||||||
t.assertInstanceOf(b, Buffer);
|
|
||||||
t.assertEquals(b.strlen(), 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('Buffer.appendLine', () => {
|
|
||||||
const b = new Buffer();
|
|
||||||
|
|
||||||
// Carriage return and line feed
|
|
||||||
b.appendLine();
|
|
||||||
t.assertEquals(b.strlen(), 2);
|
|
||||||
|
|
||||||
b.clear();
|
|
||||||
t.assertEquals(b.strlen(), 0);
|
|
||||||
|
|
||||||
b.appendLine('foo');
|
|
||||||
t.assertEquals(b.strlen(), 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('Buffer.append', () => {
|
|
||||||
const b = new Buffer();
|
|
||||||
|
|
||||||
b.append('foobar');
|
|
||||||
t.assertEquals(b.strlen(), 6);
|
|
||||||
b.clear();
|
|
||||||
|
|
||||||
b.append('foobar', 3);
|
|
||||||
t.assertEquals(b.strlen(), 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('Buffer.flush', async () => {
|
|
||||||
const b = new Buffer();
|
|
||||||
b.append('foobarbaz');
|
|
||||||
t.assertEquals(b.strlen(), 9);
|
|
||||||
|
|
||||||
await b.flush();
|
|
||||||
|
|
||||||
t.assertEquals(b.strlen(), 0);
|
|
||||||
});
|
|
||||||
});
|
|
@ -55,7 +55,7 @@ export class Document {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected appendRow(s: string): void {
|
public appendRow(s: string): void {
|
||||||
this.#rows.push(new Row(s));
|
this.#rows.push(new Row(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,11 +95,6 @@ export interface IRuntime {
|
|||||||
*/
|
*/
|
||||||
name: RunTimeType;
|
name: RunTimeType;
|
||||||
|
|
||||||
/**
|
|
||||||
* Runtime-specific FFI
|
|
||||||
*/
|
|
||||||
ffi: IFFI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runtime-specific terminal functionality
|
* Runtime-specific terminal functionality
|
||||||
*/
|
*/
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import { die, getRuntime, IFFI } from './mod.ts';
|
import { die, IFFI, importForRuntime, ITerminalSize } from './mod.ts';
|
||||||
|
|
||||||
export const STDIN_FILENO = 0;
|
export const STDIN_FILENO = 0;
|
||||||
export const TCSANOW = 0;
|
export const TCSANOW = 0;
|
||||||
|
|
||||||
export const TERMIOS_SIZE = 60;
|
export const TERMIOS_SIZE = 60;
|
||||||
|
|
||||||
|
export const defaultTerminalSize: ITerminalSize = {
|
||||||
|
rows: 24,
|
||||||
|
cols: 80,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation to toggle raw mode
|
* Implementation to toggle raw mode
|
||||||
*/
|
*/
|
||||||
@ -112,7 +117,7 @@ export const getTermios = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the runtime-specific ffi wrappers
|
// Get the runtime-specific ffi wrappers
|
||||||
const { ffi } = await getRuntime();
|
const ffi = await importForRuntime('ffi');
|
||||||
termiosSingleton = new Termios(ffi);
|
termiosSingleton = new Termios(ffi);
|
||||||
|
|
||||||
return termiosSingleton;
|
return termiosSingleton;
|
||||||
|
@ -15,12 +15,14 @@ export interface IPoint {
|
|||||||
* The shared test interface, so tests can be run by both runtimes
|
* The shared test interface, so tests can be run by both runtimes
|
||||||
*/
|
*/
|
||||||
export interface ITestBase {
|
export interface ITestBase {
|
||||||
test(name: string, fn: () => void): void;
|
|
||||||
assertEquals(actual: unknown, expected: unknown): void;
|
assertEquals(actual: unknown, expected: unknown): void;
|
||||||
|
assertExists(actual: unknown): void;
|
||||||
|
assertFalse(actual: boolean): void;
|
||||||
|
assertInstanceOf(actual: unknown, expectedType: any): void;
|
||||||
assertNotEquals(actual: unknown, expected: unknown): void;
|
assertNotEquals(actual: unknown, expected: unknown): void;
|
||||||
assertStrictEquals(actual: unknown, expected: unknown): void;
|
assertStrictEquals(actual: unknown, expected: unknown): void;
|
||||||
assertExists(actual: unknown): void;
|
|
||||||
assertInstanceOf(actual: unknown, expectedType: any): void;
|
|
||||||
assertTrue(actual: boolean): void;
|
assertTrue(actual: boolean): void;
|
||||||
assertFalse(actual: boolean): void;
|
test(name: string, fn: () => void, timeout?: number): void;
|
||||||
|
testGroup(name: string, fn: () => void): void;
|
||||||
|
testSuite(testObj: any): void;
|
||||||
}
|
}
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
import { getTestRunner } from './mod.ts';
|
|
||||||
import {
|
|
||||||
chars,
|
|
||||||
ctrl_key,
|
|
||||||
is_ascii,
|
|
||||||
is_control,
|
|
||||||
noop,
|
|
||||||
ord,
|
|
||||||
strlen,
|
|
||||||
truncate,
|
|
||||||
} from './utils.ts';
|
|
||||||
|
|
||||||
getTestRunner().then((t) => {
|
|
||||||
t.test('noop fn', () => {
|
|
||||||
t.assertExists(noop);
|
|
||||||
t.assertEquals(noop(), undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Strings
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
t.test('ord fn returns 256 on invalid string', () => {
|
|
||||||
t.assertEquals(ord(''), 256);
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('chars fn properly splits strings into unicode characters', () => {
|
|
||||||
t.assertEquals(chars('😺😸😹'), ['😺', '😸', '😹']);
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('ctrl_key fn returns expected values', () => {
|
|
||||||
const ctrl_a = ctrl_key('a');
|
|
||||||
t.assertTrue(is_control(ctrl_a));
|
|
||||||
t.assertEquals(ctrl_a, String.fromCodePoint(0x01));
|
|
||||||
|
|
||||||
const invalid = ctrl_key('😺');
|
|
||||||
t.assertFalse(is_control(invalid));
|
|
||||||
t.assertEquals(invalid, '😺');
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('is_ascii properly discerns ascii chars', () => {
|
|
||||||
t.assertTrue(is_ascii('asjyverkjhsdf1928374'));
|
|
||||||
t.assertFalse(is_ascii('😺acalskjsdf'));
|
|
||||||
t.assertFalse(is_ascii('ab😺ac'));
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('is_control fn works as expected', () => {
|
|
||||||
t.assertFalse(is_control('abc'));
|
|
||||||
t.assertTrue(is_control(String.fromCodePoint(0x01)));
|
|
||||||
t.assertFalse(is_control('😺'));
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('strlen fn returns expected length for ascii strings', () => {
|
|
||||||
t.assertEquals(strlen('abc'), 'abc'.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('strlen fn returns expected length for multibyte characters', () => {
|
|
||||||
t.assertEquals(strlen('😺😸😹'), 3);
|
|
||||||
t.assertNotEquals('😺😸😹'.length, strlen('😺😸😹'));
|
|
||||||
|
|
||||||
// Skin tone modifier + base character
|
|
||||||
t.assertEquals(strlen('🤰🏼'), 2);
|
|
||||||
t.assertNotEquals('🤰🏼'.length, strlen('🤰🏼'));
|
|
||||||
|
|
||||||
// This has 4 sub-characters, and 3 zero-width-joiners
|
|
||||||
t.assertEquals(strlen('👨👩👧👦'), 7);
|
|
||||||
t.assertNotEquals('👨👩👧👦'.length, strlen('👨👩👧👦'));
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('truncate shortens strings', () => {
|
|
||||||
t.assertEquals(truncate('😺😸😹', 1), '😺');
|
|
||||||
t.assertEquals(truncate('😺😸😹', 5), '😺😸😹');
|
|
||||||
t.assertEquals(truncate('👨👩👧👦', 5), '👨👩👧');
|
|
||||||
});
|
|
||||||
});
|
|
@ -2,14 +2,12 @@
|
|||||||
* The main entrypoint when using Deno as the runtime
|
* The main entrypoint when using Deno as the runtime
|
||||||
*/
|
*/
|
||||||
import { IRuntime, RunTimeType } from '../common/mod.ts';
|
import { IRuntime, RunTimeType } from '../common/mod.ts';
|
||||||
import DenoFFI from './ffi.ts';
|
|
||||||
import DenoTerminalIO from './terminal_io.ts';
|
import DenoTerminalIO from './terminal_io.ts';
|
||||||
import DenoFileIO from './file_io.ts';
|
import DenoFileIO from './file_io.ts';
|
||||||
|
|
||||||
const DenoRuntime: IRuntime = {
|
const DenoRuntime: IRuntime = {
|
||||||
name: RunTimeType.Deno,
|
name: RunTimeType.Deno,
|
||||||
file: DenoFileIO,
|
file: DenoFileIO,
|
||||||
ffi: DenoFFI,
|
|
||||||
term: DenoTerminalIO,
|
term: DenoTerminalIO,
|
||||||
onEvent: (eventName: string, handler) =>
|
onEvent: (eventName: string, handler) =>
|
||||||
globalThis.addEventListener(eventName, handler),
|
globalThis.addEventListener(eventName, handler),
|
||||||
|
@ -9,43 +9,34 @@ const {
|
|||||||
assertStrictEquals,
|
assertStrictEquals,
|
||||||
} = stdAssert;
|
} = stdAssert;
|
||||||
|
|
||||||
class TestBase implements ITestBase {
|
export function testSuite(testObj: any) {
|
||||||
test(name: string, fn: () => void): void {
|
Object.keys(testObj).forEach((group) => {
|
||||||
return Deno.test(name, fn);
|
const groupObj = testObj[group];
|
||||||
}
|
Object.keys(groupObj).forEach((testName) => {
|
||||||
|
Deno.test(testName, groupObj[testName]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(actual: unknown, expected: unknown): void {
|
const DenoTestBase: ITestBase = {
|
||||||
return assertEquals(actual, expected);
|
assertEquals,
|
||||||
}
|
assertExists,
|
||||||
|
assertInstanceOf,
|
||||||
assertStrictEquals(actual: unknown, expected: unknown) {
|
assertNotEquals,
|
||||||
return assertStrictEquals(actual, expected);
|
assertStrictEquals,
|
||||||
}
|
assertTrue: function (actual: boolean): void {
|
||||||
|
|
||||||
assertNotEquals(actual: unknown, expected: unknown): void {
|
|
||||||
return assertNotEquals(actual, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertExists(actual: unknown, msg?: string): void {
|
|
||||||
return assertExists(actual, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertInstanceOf(actual: unknown, expectedType: any): void {
|
|
||||||
return assertInstanceOf(actual, expectedType);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(actual: boolean): void {
|
|
||||||
if (actual !== true) {
|
if (actual !== true) {
|
||||||
throw new AssertionError(`actual: "${actual}" expected to be true"`);
|
throw new AssertionError(`actual: "${actual}" expected to be true"`);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
assertFalse(actual: boolean): void {
|
assertFalse(actual: boolean): void {
|
||||||
if (actual !== false) {
|
if (actual !== false) {
|
||||||
throw new AssertionError(`actual: "${actual}" expected to be false"`);
|
throw new AssertionError(`actual: "${actual}" expected to be false"`);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
test: Deno.test,
|
||||||
|
testGroup: (_name: string, fn) => fn(),
|
||||||
|
testSuite,
|
||||||
|
};
|
||||||
|
|
||||||
const testBase = new TestBase();
|
export default DenoTestBase;
|
||||||
export default testBase;
|
|
||||||
|
Loading…
Reference in New Issue
Block a user