tape-six 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,8 +4,13 @@
4
4
  [npm-url]: https://npmjs.org/package/tape-six
5
5
 
6
6
  `tape-six` is a [TAP](https://en.wikipedia.org/wiki/Test_Anything_Protocol)-based library for unit tests.
7
- It is written in the modern JavaScript for the modern JavaScript and works in [Node](https://nodejs.org/),
8
- [Deno](https://deno.land/), [Bun](https://bun.sh/) and browsers.
7
+ It is written in the modern JavaScript for the modern JavaScript and works in [Node](https://nodejs.org/), [Deno](https://deno.land/), [Bun](https://bun.sh/) and browsers.
8
+
9
+ It runs ES modules (`import`-based code) natively and supports CommonJS modules transparently using built-in [ESM](https://nodejs.org/api/esm.html).
10
+
11
+ It can run TypeScript code with modern versions of Node, Bun and Deno without transpilation. Obviously TS bindings are included.
12
+
13
+ Individual test files can be executed directly with `node`, `deno`, `bun` without a need for a special test runner utility. It facilitates debugging and improves testing.
9
14
 
10
15
  Why `tape-six`? It was supposed to be named `tape6` but `npm` does not allow names "similar"
11
16
  to existing packages. Instead of eliminating name-squatting they force to use unintuitive and
@@ -16,7 +21,7 @@ unmemorable names. That's why all internal names, environment variables, and pub
16
21
  Why another library? Working on projects written in modern JS (with modules) I found several problems
17
22
  with existing unit test libraries:
18
23
 
19
- - In my opinion unit test files should be directly executable with `node`, `deno`, `bun`, browsers
24
+ - In my opinion unit test files should be directly executable with Node, Bun, Deno, browsers
20
25
  (with a trivial HTML file to load a test file) without a need for a special test runner utility,
21
26
  which wraps and changes my beautiful code.
22
27
  - Debugging my tests should be trivial. It should not be different from debugging any regular file.
@@ -24,11 +29,11 @@ with existing unit test libraries:
24
29
  - I want to debug my code, not dependencies I've never heard about.
25
30
  - I want to see where a problem happens, not some guts of a test harness.
26
31
  - Tests should work with ES modules natively.
27
- - What if I want to debug some CommonJS code with Node? Fret not! Modules can import CommonJS files directly.
28
- But not the other way around (yet). And it helps to test how module users can use your beautiful
29
- CommonJS package.
32
+ - What if I want to debug some CommonJS code with Node? No problem, it just works.
33
+ - Tests should work with TypeScript natively.
34
+ - It just workss: modern runtimes (Node, Deno, Bun) support running TypeScript natively without transpilation by ignoring type information and running the code directly.
30
35
  - The [DX](https://en.wikipedia.org/wiki/User_experience#Developer_experience) in browsers are usually abysmal.
31
- - Both console-based debugging and a UI to navigate results should be properly supported.
36
+ - Both console-based debugging and a UI to navigate results are properly supported.
32
37
 
33
38
  ## Docs
34
39
 
@@ -43,6 +48,11 @@ The whole API is based on two objects: `test` and `Tester`.
43
48
 
44
49
  ```js
45
50
  import test from 'tape-six';
51
+ // import {test} from 'tape-six';
52
+
53
+ // CommonJS:
54
+ // const {test} = require('tape-six');
55
+ // const {default: test} = require('tape-six');
46
56
  ```
47
57
 
48
58
  This function registers a test suite. Available options:
@@ -50,13 +60,14 @@ This function registers a test suite. Available options:
50
60
  - `async test(name, options, testFn)` — registers a test suite to be executed asynchronously.
51
61
  The returned promise is resolved when the test suite is finished.
52
62
  - In most cases no need to wait for the returned promise.
53
- - The test function has the following signature: `testFn(tester)`
54
- - `test.skip(name, options, testFn)` — registers a test suite to be skipped.
63
+ - The test function has the following signature: `async testFn(tester)`
64
+ - The function can be synchronous or asynchronous.
65
+ - `async test.skip(name, options, testFn)` — registers a test suite to be skipped.
55
66
  - It is used to mark a test suite to be skipped. It will not be executed.
56
- - `test.todo(name, options, testFn)` — registers a test suite that is marked as work in progress.
67
+ - `async test.todo(name, options, testFn)` — registers a test suite that is marked as work in progress.
57
68
  - Tests in this suite will be executed, errors will be reported but not counted as failures.
58
69
  - It is used to mark tests for incomplete features under development.
59
- - `test.asPromise(name, options, testPromiseFn)` — registers a test suite to be executed asynchronously
70
+ - `async test.asPromise(name, options, testPromiseFn)` — registers a test suite to be executed asynchronously
60
71
  using the callback-style API to notify that the test suite is finished.
61
72
  - The test function has a different signature: `testPromiseFn(tester, resolve, reject)`.
62
73
 
@@ -68,19 +79,22 @@ The arguments mentioned above are:
68
79
  - `todo` — if `true`, the test suite will be marked as work in progress.
69
80
  - `name` — the optional name of the test suite. If not provided, it will be set to the name of the test function or `'(anonymous)'`.
70
81
  - Can be overridden by the `name` argument.
71
- - `testFn` — the optional test function to be executed.
72
- - Can be overridden by the `testFn` argument.
73
82
  - `timeout` — the optional timeout in milliseconds. It is used for asynchronous tests.
74
83
  - If the timeout is exceeded, the test suite will be marked as failed.
75
84
  - **Important:** JavaScript does not provide a generic way to cancel asynchronous operations.
76
85
  When the timeout is exceeded, `tape6` will stop waiting for the test to finish,
77
86
  but it will continue running in the background.
78
87
  - The default: no timeout.
79
- - `testFn` — the test function to be executed. It will be called with the `tester` object.
80
- The result will be ignored.
81
- - This function can be an asynchronous one or return a promise.
82
- - `testPromiseFn` — the test function to be executed. It will be called with the `tester` object
83
- and two callbacks: `resolve` and `reject` modeled on the [Promise API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise).
88
+ - `testFn` — the optional test function to be executed (see below).
89
+ - Can be overridden by the `testFn` argument.
90
+ - `testPromiseFn` — the optional callback-based test function to be executed.
91
+ - Can be overridden by the `testPromiseFn` argument.
92
+ - `testFn` — a test function to be executed. It will be called with the `tester` object.
93
+ The result will be ignored.
94
+ - This function can be synchronous or asynchronous.
95
+ - `testPromiseFn` — a callback-based test function to be executed (see below). It will be called with the `tester` object and two callbacks: `resolve` and `reject` modeled on the [Promise API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise).
96
+ - Value supplied to `resolve()` will be ignored.
97
+ - Value supplied to `reject()` will be used as the error message.
84
98
 
85
99
  Given all that `test` and its helpers can be called like this:
86
100
 
@@ -111,6 +125,32 @@ test({
111
125
  });
112
126
  ```
113
127
 
128
+ Examples of callback-based tests:
129
+
130
+ ```js
131
+ test.asPromise(name, options, testPromiseFn);
132
+ test.asPromise(name, testPromiseFn);
133
+ test.asPromise(testPromiseFn);
134
+ test.asPromise(name, options);
135
+ test.asPromise(options, testPromiseFn);
136
+ test.asPromise(options);
137
+
138
+ // examples:
139
+ test.asPromise('foo', (t, resolve, reject) => {
140
+ t.pass();
141
+ const result = someAsyncOperationAsPromise();
142
+ result.then(resolve).catch(reject);
143
+ });
144
+
145
+ test.asPromise('bar', async (t, resolve, reject) => {
146
+ const nodeStream = fs.createWriteStream('bar.txt');
147
+ nodeStream.on('error', reject);
148
+ nodeStream.on('finish', resolve);
149
+ nodeStream.write('hello');
150
+ nodeStream.end();
151
+ });
152
+ ```
153
+
114
154
  ### `Tester`
115
155
 
116
156
  `Tester` helps to do asserts and provides an interface between a test suite and the test harness.
@@ -190,6 +230,10 @@ The following methods are available (all `msg` arguments are optional):
190
230
  - `comment(msg)` — sends a comment to the test harness. Rarely used.
191
231
  - `skipTest(...args, msg)` — skips the current test yet sends a message to the test harness.
192
232
  - `bailOut(msg)` — stops the test suite and sends a message to the test harness.
233
+ - Evaluators:
234
+ - `OK(condition, msg, options)` — a high-level helper for evaluating simple expressions.
235
+ - _Available since 1.4.0._
236
+ - See [Tester](https://github.com/uhop/tape-six/wiki/Tester) for description and examples.
193
237
 
194
238
  In all cases, the `msg` message is optional. If it is not provided, some suitable generic message will be used.
195
239
 
@@ -216,15 +260,49 @@ It is super easy to run tests:
216
260
  2. Write a test. For example, you named it `test.js`.
217
261
  3. Run the test: `node test.js`
218
262
  1. Or: `bun run test.js`
219
- 2. Or: `deno run -A test.js`
263
+ 2. Or: `deno run -A test.js` (you can use appropriate permissions).
220
264
  3. Or you can run them in a browser!
221
265
  4. Profit!
222
266
 
223
267
  If you have a lot of tests, you can organize them using multiple files and directories.
224
268
  `tape-six` provides multiple test runners that can run them in different environments.
225
269
 
270
+ Tests can run in parallel using multiple threads to speed up the whole process.
271
+
272
+ ```bash
273
+ tape6 # run tests in parallel using all available threads
274
+ tape6 --par 4 # run tests in parallel using 4 threads
275
+ tape6 --par 1 # run one test at a time
276
+ ```
277
+
278
+ If you want to run tests in separate processes, check out [tape-six-proc](https://www.npmjs.com/package/tape-six-proc). Why do you want to do that? When tests have to modify globals or use single-threaded binary extensions.
279
+
226
280
  ### Configuring test runners
227
281
 
282
+ TLDR version — add to your `package.json`:
283
+
284
+ ```jsonc
285
+ {
286
+ // ...
287
+ "scripts": {
288
+ "test": "tape6 --flags FO",
289
+ "start": "tape6-server --trace"
290
+ }
291
+ // ...
292
+ "tape6": {
293
+ "tests": ["/tests/test-*.*js"],
294
+ "importmap": {
295
+ "imports": {
296
+ "tape-six": "/node_modules/tape-six/index.js",
297
+ "tape-six/": "/node_modules/tape-six/src/",
298
+ "my-package": "/index.js",
299
+ "my-package/": "/src/"
300
+ }
301
+ }
302
+ }
303
+ }
304
+ ```
305
+
228
306
  See [set-up tests](https://github.com/uhop/tape-six/wiki/Set-up-tests) for details.
229
307
 
230
308
  ### Command-line utilities
@@ -232,10 +310,14 @@ See [set-up tests](https://github.com/uhop/tape-six/wiki/Set-up-tests) for detai
232
310
  - [tape6](https://github.com/uhop/tape-six/wiki/Utility-%E2%80%90-tape6) — the main utility of the package to run tests in different environments.
233
311
  - [tape6-server](https://github.com/uhop/tape-six/wiki/Utility-%E2%80%90-tape6-server) — a custom web server with a web application that helps running tests in browsers.
234
312
 
313
+ Test output can be controlled by flags. See [Supported flags](https://github.com/uhop/tape-six/wiki/Supported-flags) for details.
314
+
235
315
  ## Release notes
236
316
 
237
317
  The most recent releases:
238
318
 
319
+ - 1.4.0 _Added a high-level helper `OK()` for evaluating simple expressions._
320
+ - 1.3.5 _Minor improvements, better docs._
239
321
  - 1.3.4 _Minor bugfixes and improvements._
240
322
  - 1.3.3 _Added a way to hide console/streams output, better support for file tests, better TTY formatting._
241
323
  - 1.3.2 _Internal refactoring (capture console calls), updated dependencies._
@@ -250,18 +332,5 @@ The most recent releases:
250
332
  - 1.0.2 _Bugfix for Deno using the JSONL reporter._
251
333
  - 1.0.1 _Technical release: added more links._
252
334
  - 1.0.0 _The first official release._
253
- - 0.12.3 _Technical release: exposed internal classes for external utilities._
254
- - 0.12.2 _Fixed a minor serialization issue._
255
- - 0.12.1 _Minor Deno-related refactoring, fixed the way tests are triggered._
256
- - 0.12.0 _Removed data to avoid serializing non-serializable objects._
257
- - 0.11.0 _Minor improvements to the server: temporary redirects, a hyperlink to the web app._
258
- - 0.10.0 _Refactored test runners, refactored stopping tests on failure, added JSONL reporter, fixed bugs._
259
- - 0.9.6 _Updated deps._
260
- - 0.9.5 _Updated the lock file._
261
- - 0.9.4 _Updated deps. Added test runners for Bun and Deno._
262
- - 0.9.3 _Made TTY reporter work with non-TTY streams._
263
- - 0.9.2 _Fixed Windows runner._
264
- - 0.9.1 _More updates related to renaming `tape6` ⇒ `tape-six`._
265
- - 0.9.0 _Initial release._
266
335
 
267
336
  For more info consult full [release notes](https://github.com/uhop/tape-six/wiki/Release-notes).
package/bin/tape6-deno.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env -S deno run --allow-read --allow-env --ext=js
1
+ #!/usr/bin/env -S deno run --allow-all --ext=js
2
2
 
3
3
  import {fileURLToPath} from 'node:url';
4
4
 
package/index.d.ts CHANGED
@@ -345,6 +345,25 @@ export declare interface Test {
345
345
  */
346
346
  resolves(promise: Promise<unknown>, message?: string): Promise<void>;
347
347
 
348
+ /**
349
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
350
+ * @param condition - The JS condition to check as a string
351
+ * @param message - Message to display if the assertion fails
352
+ * @param options - Optional options object. `self` is the name of the tester argument, which should be used in the code. Default: `"t"`.
353
+ */
354
+ OK(condition: string, message: string, options?: {self?: string}): string;
355
+ /**
356
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
357
+ * @param condition - The JS condition to check as a string
358
+ * @param options - Optional options object. `self` is the name of the tester argument, which should be used in the code. Default: `"t"`.
359
+ */
360
+ OK(condition: string, options: {self?: string}): string;
361
+ /**
362
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
363
+ * @param condition - The JS condition to check as a string
364
+ */
365
+ OK(condition: string): string;
366
+
348
367
  // aliases
349
368
 
350
369
  /**
@@ -601,6 +620,44 @@ export declare interface Test {
601
620
  * @param message - Optional message to display if the assertion fails
602
621
  */
603
622
  doesNotReject(promise: Promise<unknown>, message?: string): Promise<void>;
623
+
624
+ /**
625
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
626
+ * @param condition - The JS condition to check as a string
627
+ * @param message - Message to display if the assertion fails
628
+ * @param options - Optional options object. `self` is the name of the tester argument, which should be used in the code. Default: `"t"`.
629
+ */
630
+ TRUE(condition: string, message: string, options?: {self?: string}): string;
631
+ /**
632
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
633
+ * @param condition - The JS condition to check as a string
634
+ * @param options - Optional options object. `self` is the name of the tester argument, which should be used in the code. Default: `"t"`.
635
+ */
636
+ TRUE(condition: string, options: {self?: string}): string;
637
+ /**
638
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
639
+ * @param condition - The JS condition to check as a string
640
+ */
641
+ TRUE(condition: string): string;
642
+
643
+ /**
644
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
645
+ * @param condition - The JS condition to check as a string
646
+ * @param message - Message to display if the assertion fails
647
+ * @param options - Optional options object. `self` is the name of the tester argument, which should be used in the code. Default: `"t"`.
648
+ */
649
+ ASSERT(condition: string, message: string, options?: {self?: string}): string;
650
+ /**
651
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
652
+ * @param condition - The JS condition to check as a string
653
+ * @param options - Optional options object. `self` is the name of the tester argument, which should be used in the code. Default: `"t"`.
654
+ */
655
+ ASSERT(condition: string, options: {self?: string}): string;
656
+ /**
657
+ * Returns a code as a string for evaluation that checks if the condition is truthy.
658
+ * @param condition - The JS condition to check as a string
659
+ */
660
+ ASSERT(condition: string): string;
604
661
  }
605
662
 
606
663
  export declare interface Test {
@@ -711,4 +768,5 @@ export declare interface Test {
711
768
 
712
769
  declare const test: Test;
713
770
 
771
+ export {test};
714
772
  export default test;
package/index.js CHANGED
@@ -191,4 +191,5 @@ if (!getConfiguredFlag()) {
191
191
  registerNotifyCallback(testCallback);
192
192
  }
193
193
 
194
+ export {test};
194
195
  export default test;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tape-six",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "TAP the test harness for the modern JavaScript (ES6).",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -61,7 +61,14 @@
61
61
  ],
62
62
  "tape6": {
63
63
  "tests": [
64
- "/tests/test-*.*js"
64
+ "/tests/test-*.js",
65
+ "/tests/test-*.mjs"
66
+ ],
67
+ "cli": [
68
+ "/tests/test-*.cjs"
69
+ ],
70
+ "browser": [
71
+ "/tests/web/test-*.html"
65
72
  ],
66
73
  "importmap": {
67
74
  "imports": {
package/src/OK.js ADDED
@@ -0,0 +1,47 @@
1
+ import {Tester, setAliases} from './Tester.js';
2
+
3
+ // code mostly borrowed from https://github.com/heya/ice/blob/master/assert.js under BSD-3
4
+
5
+ const listVariables = (code, self) => {
6
+ const vars =
7
+ code
8
+ .replace(
9
+ /(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*\b|\b[a-zA-Z_$][a-zA-Z_$\d]*:|\b(?:function|return|if|else|switch|case|while|for|do|break|continue|var|try|catch|finally|throw|with|debugger|default|this|true|false|null|undefined|typeof|instanceof|in|delete|new|void|arguments|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|escape|eval|isFinite|isNaN|parseFloat|parseInt|unescape|window|document)\b|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g,
10
+ ''
11
+ )
12
+ .match(/(\b[a-z_$][a-z_$\d]*\b)/gi) || [];
13
+ const result = [],
14
+ resultSet = {};
15
+ for (const name of vars) {
16
+ const key = '-' + name;
17
+ if (name != self && !resultSet[key]) {
18
+ result.push("'" + name + "':" + name);
19
+ resultSet[key] = 1;
20
+ }
21
+ }
22
+ return '{' + result.join(',') + '}';
23
+ };
24
+
25
+ Tester.prototype.OK = function OK(condition, msg, options) {
26
+ if (typeof condition != 'string') {
27
+ throw new TypeError('Condition must be a string');
28
+ }
29
+ if (typeof msg == 'object') {
30
+ options = msg;
31
+ msg = undefined;
32
+ }
33
+ const {self = 't'} = options || {};
34
+ return `(${self}.state.emit({
35
+ name: ${JSON.stringify(msg || condition)},
36
+ test: ${self}.testNumber,
37
+ marker: new Error(),
38
+ time: ${self}.state.timer.now(),
39
+ operator: 'ok',
40
+ fail: !(${condition}),
41
+ data: {
42
+ actual: ${listVariables(condition, self)}
43
+ }
44
+ }))`;
45
+ };
46
+
47
+ setAliases('OK', 'TRUE, ASSERT');
package/src/Tester.js CHANGED
@@ -9,7 +9,7 @@ const throwHelper = fn => {
9
9
  return null;
10
10
  };
11
11
 
12
- class Tester {
12
+ export class Tester {
13
13
  constructor(state, testNumber) {
14
14
  this.state = state;
15
15
  this.testNumber = testNumber;
@@ -400,7 +400,7 @@ class Tester {
400
400
  }
401
401
  Tester.prototype.any = Tester.prototype._ = any;
402
402
 
403
- const setAliases = (source, aliases) =>
403
+ export const setAliases = (source, aliases) =>
404
404
  aliases.split(', ').forEach(alias => (Tester.prototype[alias] = Tester.prototype[source]));
405
405
 
406
406
  setAliases('ok', 'true, assert');
package/src/test.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import {selectTimer} from './utils/timer.js';
2
2
  import State, {StopTest} from './State.js';
3
- import Tester from './Tester.js';
4
3
  import getDeferred from './utils/getDeferred.js';
5
4
  import timeout from './utils/timeout.js';
6
5
  import {formatTime} from './utils/formatters.js';
7
6
  import defer from './utils/defer.js';
8
7
 
8
+ import Tester from './Tester.js';
9
+ import './OK.js';
10
+
9
11
  let tests = [],
10
12
  reporter = null,
11
13
  testCounter = 0,
@@ -37,7 +37,7 @@ export const getConfig = async (rootFolder, traceFn) => {
37
37
  }
38
38
 
39
39
  // check well-known files
40
- if (!cfg) cfg = {tests: ['/tests/test-*.*js']};
40
+ if (!cfg) cfg = {tests: ['/tests/test-*.js', '/tests/test-*.mjs'], cli: ['/tests/test-*.cjs']};
41
41
 
42
42
  return cfg;
43
43
  };
@@ -55,6 +55,14 @@ export const resolveTests = async (rootFolder, type, traceFn) => {
55
55
  }
56
56
  }
57
57
 
58
+ if (type !== 'browser') {
59
+ if (Array.isArray(cfg.cli)) {
60
+ patterns = patterns.concat(cfg.cli);
61
+ } else if (typeof cfg.cli == 'string') {
62
+ patterns.push(cfg.cli);
63
+ }
64
+ }
65
+
58
66
  if (Array.isArray(cfg.tests)) {
59
67
  patterns = patterns.concat(cfg.tests);
60
68
  } else if (typeof cfg.tests == 'string') {