Skip to content

Commit

Permalink
add types
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Feb 29, 2024
1 parent 732268b commit 703c634
Show file tree
Hide file tree
Showing 14 changed files with 606 additions and 48 deletions.
1 change: 1 addition & 0 deletions .github/workflows/node-aught.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ jobs:
range: '< 10'
type: minors
command: npm run tests-only
skip-ls-check: true
1 change: 1 addition & 0 deletions bin/import-or-require.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { extname: extnamePath } = require('path');
const { pathToFileURL } = require('url');
const getPackageType = require('get-package-type');

/** @type {(file: string) => undefined | Promise<unknown>} */
// eslint-disable-next-line consistent-return
module.exports = function importOrRequire(file) {
const ext = extnamePath(file);
Expand Down
2 changes: 2 additions & 0 deletions bin/tape
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ var hasImport = require('has-dynamic-import');

var tape = require('../');

/** @type {(hasSupport: boolean) => Promise<void> | void} */
function importFiles(hasSupport) {
if (!hasSupport) {
return files.forEach(function (x) { require(x); });
Expand All @@ -104,6 +105,7 @@ function importFiles(hasSupport) {

tape.wait();

/** @type {null | undefined | Promise<unknown>} */
var filesPromise = files.reduce(function (promise, file) {
return promise ? promise.then(function () {
return importOrRequire(file);
Expand Down
82 changes: 82 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { ThroughStream } from '@ljharb/through';

import type Test from './lib/test';
import type Results from './lib/results';

declare function harnessFunction(this: ThisType<Test>, name: string, opts: tape.TestOptions, cb: tape.TestCase): Test;
declare function harnessFunction(this: ThisType<Test>, name: string, opts: tape.TestOptions): Test;
declare function harnessFunction(this: ThisType<Test>, name: string, cb: tape.TestCase): Test;
declare function harnessFunction(this: ThisType<Test>, name: string): Test;
declare function harnessFunction(this: ThisType<Test>, opts: tape.TestOptions, cb: tape.TestCase): Test;
declare function harnessFunction(this: ThisType<Test>, opts: tape.TestOptions): Test;
declare function harnessFunction(this: ThisType<Test>, cb: tape.TestCase): Test;

declare namespace tape {
export type TestOptions = {
objectPrintDepth?: number | undefined;
skip?: boolean | undefined;
timeout?: number | undefined;
todo?: boolean | undefined;
};

export interface AssertOptions {
skip?: boolean | string | undefined;
todo?: boolean | string | undefined;
message?: string | undefined;
actual?: unknown;
expected?: unknown;
exiting?: boolean;
}

export interface TestCase {
(test: Test): void | Promise<void>;
}

export interface StreamOptions {
objectMode?: boolean | undefined;
}

function createStream(opts?: StreamOptions): ThroughStream;

export type CreateStream = typeof createStream;

export type HarnessEventHandler = (cb: Test.SyncCallback, ...rest: unknown[]) => void;

function only(name: string, cb: tape.TestCase): void;
function only(name: string, opts: tape.TestOptions, cb: tape.TestCase): void;
function only(cb: tape.TestCase): void;
function only(opts: tape.TestOptions, cb: tape.TestCase): void;

export type HarnessCallSignatures = typeof harnessFunction;

export interface Harness extends HarnessCallSignatures {
run?: () => void;
only: typeof only;
_exitCode: number;
_results: Results;
_tests: Test[];
close: () => void;
createStream: CreateStream;
onFailure: HarnessEventHandler;
onFinish: HarnessEventHandler;
}

export type HarnessConfig = {
autoclose?: boolean;
noOnly?: boolean;
stream?: NodeJS.WritableStream | ThroughStream;
exit?: boolean;
} & StreamOptions;

function createHarness(conf_?: HarnessConfig): Harness;
const Test: Test;
const test: typeof tape;
const skip: Test['skip'];
}

declare function tape(this: tape.Harness, name: string, opts: tape.TestOptions, cb: tape.TestCase): Test;
declare function tape(this: tape.Harness, name: string, cb: tape.TestCase): Test;
declare function tape(this: tape.Harness, opts?: tape.TestOptions): Test;
declare function tape(this: tape.Harness, opts: tape.TestOptions, cb: tape.TestCase): Test;

export = tape;
48 changes: 38 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
'use strict';

var defined = require('defined');
var through = require('@ljharb/through');

var createDefaultStream = require('./lib/default_stream');
var Test = require('./lib/test');
var Results = require('./lib/results');
var through = require('@ljharb/through');

var canEmitExit = typeof process !== 'undefined' && process
&& typeof process.on === 'function' && process.browser !== true;
// eslint-disable-next-line no-extra-parens
&& typeof process.on === 'function' && /** @type {{ browser?: boolean }} */ (process).browser !== true;
var canExit = typeof process !== 'undefined' && process
&& typeof process.exit === 'function';

/** @typedef {import('.')} Tape */
/** @typedef {import('.').Harness} Harness */
/** @typedef {import('.').HarnessConfig} HarnessConfig */
/** @typedef {import('.').HarnessCallSignatures} HarnessCallSignatures */
/** @typedef {import('.').TestOptions} TestOptions */
/** @typedef {import('.').HarnessEventHandler} HarnessEventHandler */
/** @typedef {import('.').CreateStream} CreateStream */
/** @typedef {import('.').createHarness} CreateHarness */
/** @typedef {import('./lib/results').Result} Result */
/** @typedef {import('stream').Writable} WritableStream */
/** @typedef {import('.').TestCase} TestCase */

module.exports = (function () {
var wait = false;
/** @type {undefined | Harness} */
var harness;

/** @type {(opts?: HarnessConfig) => Harness} */
function getHarness(opts) {
// this override is here since tests fail via nyc if createHarness is moved upwards
if (!harness) {
Expand All @@ -24,6 +40,7 @@ module.exports = (function () {
return harness;
}

/** @type {(this: Harness, ...args: Parameters<Tape>) => ReturnType<Tape>} */
function lazyLoad() {
// eslint-disable-next-line no-invalid-this
return getHarness().apply(this, arguments);
Expand All @@ -43,6 +60,7 @@ module.exports = (function () {
return getHarness().only.apply(this, arguments);
};

/** @type {CreateStream} */
lazyLoad.createStream = function (opts) {
var options = opts || {};
if (!harness) {
Expand All @@ -66,21 +84,23 @@ module.exports = (function () {
return lazyLoad;
}());

/** @type {CreateHarness} */
function createHarness(conf_) {
var results = new Results({ todoIsOK: !!(process.env.TODO_IS_OK === '1') });
if (!conf_ || conf_.autoclose !== false) {
results.once('done', function () { results.close(); });
}

/** @type {(name: string, conf: TestOptions, cb: TestCase) => Test} */
function test(name, conf, cb) {
var t = new Test(name, conf, cb);
test._tests.push(t);

(function inspectCode(st) {
st.on('test', function sub(st_) {
st.on('test', /** @type {(st: Test) => void} */ function sub(st_) {
inspectCode(st_);
});
st.on('result', function (r) {
st.on('result', /** @type {(r: Result) => void} */ function (r) {
if (!r.todo && !r.ok && typeof r !== 'string') { test._exitCode = 1; }
});
}(t));
Expand All @@ -90,21 +110,25 @@ function createHarness(conf_) {
}
test._results = results;

test._tests = [];
/** @type {Test[]} */ test._tests = [];

/** @type {CreateStream} */
test.createStream = function (opts) {
return results.createStream(opts);
};

/** @type {HarnessEventHandler} */
test.onFinish = function (cb) {
results.on('done', cb);
};

/** @type {HarnessEventHandler} */
test.onFailure = function (cb) {
results.on('fail', cb);
};

var only = false;
/** @type {() => Test} */
test.only = function () {
if (only) { throw new Error('there can only be one only test'); }
if (conf_ && conf_.noOnly) { throw new Error('`only` tests are prohibited'); }
Expand All @@ -117,9 +141,11 @@ function createHarness(conf_) {

test.close = function () { results.close(); };

// @ts-expect-error TODO FIXME: why is `test` not assignable to `Harness`???
return test;
}

/** @type {(conf: Omit<HarnessConfig, 'autoclose'>, wait?: boolean) => Harness} */
function createExitHarness(config, wait) {
var noOnly = config.noOnly;
var objectMode = config.objectMode;
Expand All @@ -137,11 +163,12 @@ function createExitHarness(config, wait) {
if (running) { return; }
running = true;
var stream = harness.createStream({ objectMode: objectMode });
var es = stream.pipe(cStream || createDefaultStream());
// eslint-disable-next-line no-extra-parens
var es = stream.pipe(/** @type {WritableStream} */ (cStream || createDefaultStream()));
if (canEmitExit && es) { // in node v0.4, `es` is `undefined`
// TODO: use `err` arg?
// eslint-disable-next-line no-unused-vars
es.on('error', function (err) { harness._exitCode = 1; });
es.on('error', function (_) { harness._exitCode = 1; });
}
stream.on('end', function () { ended = true; });
}
Expand Down Expand Up @@ -180,6 +207,7 @@ function createExitHarness(config, wait) {
}

module.exports.createHarness = createHarness;
module.exports.Test = Test;
module.exports.test = module.exports; // tap compat
module.exports.test.skip = Test.skip;
var moduleExports = module.exports; // this hack is needed because TS has a bug with seemingly circular exports
moduleExports.Test = Test;
moduleExports.test = module.exports; // tap compat
moduleExports.skip = Test.skip;
5 changes: 5 additions & 0 deletions lib/default_stream.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ThroughStream } from "@ljharb/through";

declare function defaultStream(): ThroughStream;

export = defaultStream;
11 changes: 7 additions & 4 deletions lib/default_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
var through = require('@ljharb/through');
var fs = require('fs');

/** @type {import('./default_stream')} */
module.exports = function () {
var line = '';
var stream = through(write, flush);
return stream;

/** @type {(buf: unknown) => void} */
function write(buf) {
if (
buf == null // eslint-disable-line eqeqeq
Expand All @@ -16,10 +18,11 @@ module.exports = function () {
flush();
return;
}
for (var i = 0; i < buf.length; i++) {
var c = typeof buf === 'string'
? buf.charAt(i)
: String.fromCharCode(buf[i]);
var b = /** @type {string | ArrayLike<number>} */ (buf); // eslint-disable-line no-extra-parens
for (var i = 0; i < b.length; i++) {
var c = typeof b === 'string'
? b.charAt(i)
: String.fromCharCode(b[i]);
if (c === '\n') {
flush();
} else {
Expand Down
52 changes: 52 additions & 0 deletions lib/results.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import through from '@ljharb/through';
import type { EventEmitter } from 'events';

import type { StreamOptions } from '../';
import Test = require('./test');

declare class Results extends EventEmitter {
constructor(options?: { todoIsOK?: boolean });

count: number;
fail: number;
pass: number;
tests: Test[];
todo: number;
todoIsOK: boolean;
closed?: boolean;

_isRunning: boolean;
_only: Test | null;
_stream: through.ThroughStream;

close(this: Results): void;
createStream(this: Results, opts?: StreamOptions): through.ThroughStream;
only(this: Results, t: Test): void;
push(this: Results, t: Test): void;

_watch(this: Results, t: Test): void;
}

declare namespace Results {
export type Operator = string;

export type Result = {
id: number;
ok: boolean;
skip: unknown;
todo: unknown;
name?: string;
operator: undefined | Operator;
objectPrintDepth?: number;
actual?: unknown;
expected?: unknown;
error?: unknown;
functionName?: string;
file?: string;
line?: number;
column?: number;
at?: string;
};
}

export = Results;
Loading

0 comments on commit 703c634

Please sign in to comment.