-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
TestDirector.mjs
128 lines (113 loc) · 2.9 KB
/
TestDirector.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// @ts-check
import { bold, green, red } from "kleur/colors";
import reportError from "./reportError.mjs";
/** An ultra lightweight unit test director for Node.js. */
export default class TestDirector {
constructor() {
/**
* A map of test functions that have been added, keyed by their test names.
* @type {Map<string, Function>}
*/
this.tests = new Map();
}
/**
* Adds a test.
* @param {string} name Unique test name.
* @param {Function} test Test to run; may return a `Promise`.
* @example
* A sync test:
*
* ```js
* import { equal } from "node:assert";
* import TestDirector from "test-director";
*
* const tests = new TestDirector();
*
* tests.add("JavaScript addition.", () => {
* equal(1 + 1, 2);
* });
*
* tests.run();
* ```
* @example
* An async test:
*
* ```js
* import { ok } from "node:assert";
* import TestDirector from "test-director";
*
* const tests = new TestDirector();
*
* tests.add("GitHub is up.", async () => {
* const response = await fetch("https://github.com");
* ok(response.ok);
* });
*
* tests.run();
* ```
*/
add(name, test) {
if (typeof name !== "string")
throw new TypeError("Argument 1 `name` must be a string.");
if (this.tests.has(name))
throw new Error(`A test called \`${name}\` has already been added.`);
if (typeof test !== "function")
throw new TypeError("Argument 2 `test` must be a function.");
this.tests.set(name, test);
}
/**
* Runs the tests one after another, in the order they were added.
* @param {boolean} [throwOnFailure] After tests run, throw an error if some
* failed. Defaults to `false`.
* @returns {Promise<void>} Resolves once tests have run.
* @example
* Nested tests:
*
* ```js
* import TestDirector from "test-director";
*
* const tests = new TestDirector();
*
* tests.add("Test A.", async () => {
* const tests = new TestDirector();
*
* tests.add("Test B.", () => {
* // …
* });
*
* tests.add("Test C.", () => {
* // …
* });
*
* await tests.run(true);
* });
*
* tests.add("Test D.", () => {
* // …
* });
*
* tests.run();
* ```
*/
async run(throwOnFailure = false) {
let passCount = 0;
for (const [name, test] of this.tests) {
console.group(`\nTest: ${bold(name)}`);
try {
await test();
passCount++;
} catch (error) {
reportError(error);
} finally {
console.groupEnd();
}
}
const summary = `${passCount}/${this.tests.size} tests passed.`;
if (passCount < this.tests.size) {
const message = bold(red(summary));
if (throwOnFailure) throw new Error(message);
console.error(`\n${message}\n`);
process.exitCode = 1;
} else console.info(`\n${bold(green(summary))}\n`);
}
}