Test by Vangware
β Equality test with enforced readability, based on the concept of RITEway and inspired by uvu.
Usage
π¦ Node
Install @vangware/tests
as a dev dependency:
pnpm add -D @vangware/test
# or
npm install -D @vangware/test
# or
yarn add --dev @vangware/test
Add a test
script to package.json
:
{
"scripts": {
"test": "test"
}
}
Add TypeScript support
To support TypeScript, install tsx as a dev dependency:
pnpm add -D tsx
# or
npm install -D tsx
# or
yarn add --dev tsx
And update package.json
:
{
"scripts": {
"test": "NODE_OPTIONS='--loader tsx --no-warnings' test"
}
}
Add coverage
To add coverage, install c8
as a dev dependency:
pnpm add -D c8
# or
npm install -D c8
# or
yarn add --dev c8
And update package.json
:
{
"scripts": {
"test": "c8 test"
}
}
If you added TypeScript support, then update package.json
like this instead:
And update package.json
:
{
"scripts": {
"test": "NODE_OPTIONS='--loader tsx --no-warnings' c8 test"
}
}
Run tests:
pnpm test
# or
npm test
# or
yarn test
π¦ Deno
Import @vangware/test
using the npm:
prefix, and use it directly:
import { test } from "npm:@vangware/test";
import { add } from "../src/add.js";
test({
given: "a 1 and a 2",
must: "return 3",
received: () => add(2)(1),
wanted: () => 3,
}).then(console.log);
π Browser
Import @vangware/test
using esm.sh, and use it directly:
import { test } from "https://esm.sh/@vangware/test";
import { add } from "../src/add.js";
test({
given: "a 1 and a 2",
must: "return 3",
received: () => add(2)(1),
wanted: () => 3,
}).then(console.log);
Writing tests
TypeScript
import type { Tests } from "@vangware/test";
import { add } from "../src/add.js";
export default [
{
given: "a 1 and a 2",
must: "return 3",
received: () => add(2)(1),
wanted: () => 3,
},
{
given: "a 1 and a -2",
must: "return -1",
received: () => add(-2)(1),
wanted: () => -1,
},
] satisfies Tests<number>;
JavaScript
import { add } from "../src/add.js";
/** @satisfies {import("@vangware/test").Tests<number>} */
export default [
{
given: "a 1 and a 2",
must: "return 3",
received: () => add(2)(1),
wanted: () => 3,
},
{
given: "a 1 and a -2",
must: "return -1",
received: () => add(-2)(1),
wanted: () => -1,
},
];
Other alternatives
Instead of exporting an Array
of Test
as default
, the export can also be a
single Test
:
import type { Test } from "@vangware/test";
import { add } from "../src/add.js";
export default {
given: "a 1 and a 2",
must: "return 3",
received: () => add(2)(1),
wanted: () => 3,
} satisfies Test<number>;
Or multiple exports with different tests:
import type { Test } from "@vangware/test";
import { add } from "../src/add.js";
export const test1: Test<number> = {
given: "a 1 and a 2",
must: "return 3",
received: () => add(2)(1),
wanted: () => 3,
};
export const test2: Test<number> = {
given: "a 1 and a -2",
must: "return -1",
received: () => add(-2)(1),
wanted: () => -1,
};
It can also be used directly without the test
bin by importing the different
utils directly (like with the Deno and Browser examples above):
import { test } from "@vangware/test";
import { customFormatter } from "./customFormatter.js";
test({
given: "a 1 and a 2",
must: "return 3",
received: () => add(2)(1),
wanted: () => 3,
}).then(customFormatter);
Default output
@vangware/tests
provides a default output for the tests. It looks like this:
[TEST] ./tests/example.test.ts
[FAIL] Given a 1 and a 2, must return 3, but...
β it has the wrong value. Wanted 3 but received 4.
And if the wanted/received type is more complex, like an object, then the output goes into details about the error:
[TEST] ./tests/example.test.ts
[FAIL] Given an object, must add a single property, but...
β foo.bar has the wrong value. Wanted 1 but received 2.
β foo.baz.1 is missing.
β bar was set with the value "bar".
But developers can choose to run test
directly and use their own formatter, as
it was pointed out in the previous section.
Useful links
- π Documentation: TypeDoc generated documentation.
- β³ Changelog: List of changes between versions.
- β Tests Coverage: Coveralls page with tests coverage.
File System
ReadOnlyURL
Ζ¬ ReadOnlyURL: ReadOnly
<URL
>
Read-only URL
.
ReadOnlyURLs
Ζ¬ ReadOnlyURLs:
IsomorphicIterable
<ReadOnlyURL
>
Iterable of ReadOnlyURL
s.
See
TestTuple
Ζ¬ TestTuple<Value
>: readonly [path: ReadOnlyURL, test: Test<Value>]
Tuple used to describe a test result and its path.
See
Type parameters
Name | Type |
---|---|
Value | unknown |
TestsImport
Ζ¬ TestsImport<Value
>: Promise
<ReadOnlyRecord
<PropertyKey
,
Test
<Value
> |
Tests
<Value
>>>
Promise import of a file containing Test
or Tests
.
See
Type parameters
Name | Type |
---|---|
Value | unknown |
getFilePaths
βΈ getFilePaths(fileOrDirectory
): AsyncGenerator
<{ hash
: string
;
host
: string
; hostname
: string
; href
: string
; origin
: string
; password
: string
; pathname
: string
; port
: string
; protocol
:
string
; search
: string
; searchParams
:
Readonly
<ReadonlyMap
<string
, null
| string
>> ; username
: string
; toJSON
: () => string
; toString
: () => string
}, void
, unknown
>
Recursively search for files in the given directory and yields every file. If a file path is passed initially, that file is shielded directly.
Parameters
Name | Type | Description |
---|---|---|
fileOrDirectory | Object | Directory to get files from. |
fileOrDirectory.hash | string | MDN Reference |
fileOrDirectory.host | string | MDN Reference |
fileOrDirectory.hostname | string | MDN Reference |
fileOrDirectory.href | string | MDN Reference |
fileOrDirectory.origin | string | MDN Reference |
fileOrDirectory.password | string | MDN Reference |
fileOrDirectory.pathname | string | MDN Reference |
fileOrDirectory.port | string | MDN Reference |
fileOrDirectory.protocol | string | MDN Reference |
fileOrDirectory.search | string | MDN Reference |
fileOrDirectory.searchParams | Readonly <ReadonlyMap <string , null | string >> | MDN Reference |
fileOrDirectory.username | string | MDN Reference |
fileOrDirectory.toJSON | () => string | - |
fileOrDirectory.toString | () => string | - |
Returns
AsyncGenerator
<{ hash
: string
; host
: string
; hostname
: string
;
href
: string
; origin
: string
; password
: string
; pathname
:
string
; port
: string
; protocol
: string
; search
: string
;
searchParams
: Readonly
<ReadonlyMap
<string
, null
| string
>> ;
username
: string
; toJSON
: () => string
; toString
: () => string
},
void
, unknown
>
Example
getFiles("./tests/"); // ["./tests/vangware.test.ts"]
Yields
Files recursively found in the given directory.
testImport
βΈ testImport(path
):
AsyncGenerator
<Test
<unknown
>, void
,
unknown
>
Import a file that exports a Test
or an Iterable of Test
.
Parameters
Name | Type | Description |
---|---|---|
path | Object | Path to the test file. |
path.hash | string | MDN Reference |
path.host | string | MDN Reference |
path.hostname | string | MDN Reference |
path.href | string | MDN Reference |
path.origin | string | MDN Reference |
path.password | string | MDN Reference |
path.pathname | string | MDN Reference |
path.port | string | MDN Reference |
path.protocol | string | MDN Reference |
path.search | string | MDN Reference |
path.searchParams | Readonly <ReadonlyMap <string , null | string >> | MDN Reference |
path.username | string | MDN Reference |
path.toJSON | () => string | - |
path.toString | () => string | - |
Returns
AsyncGenerator
<Test
<unknown
>, void
,
unknown
>
Example
testImport(new URL("file:///example/test.test.js"));
// AsyncIterable<[
// {
// given: "example 1",
// must: "example 1",
// received: () => "value 1",
// wanted: () => "value 1"
// },
// {
// given: "example 2",
// must: "example 2",
// received: () => "value 2",
// wanted: () => "value 2"
// },
// ]>
Yields
Imported tests.
testsImport
βΈ testsImport(urls
):
AsyncGenerator
<TestTuple
, void
, unknown
>
Imports all the tests of the given Iterable of urls and yields TestTuple
.
Parameters
Name | Type | Description |
---|---|---|
urls | ReadOnlyURLs | Array of urls of tests. |
Returns
AsyncGenerator
<TestTuple
, void
, unknown
>
Example
testsImport([
"file:///example/test-1.test.js",
"file:///example/test-2.test.js",
]);
// AsyncIterable<
// [
// [
// "file:///example/test-1.test.js",
// {
// given: "example",
// must: "example",
// received: () => "value",
// wanted: () => "value",
// },
// ],
// [
// "file:///example/test-2.test.js",
// {
// given: "example",
// must: "example",
// received: () => "value",
// wanted: () => "value",
// },
// ],
// ]
// >;
Yields
TestTuple
containing url and test for it.
Other
filterTestFilePaths
βΈ filterTestFilePaths(readOnlyURLs
): AsyncGenerator
<{ hash
: string
;
host
: string
; hostname
: string
; href
: string
; origin
: string
; password
: string
; pathname
: string
; port
: string
; protocol
:
string
; search
: string
; searchParams
:
Readonly
<ReadonlyMap
<string
, null
| string
>> ; username
: string
; toJSON
: () => string
; toString
: () => string
}, void
, unknown
>
Takes an iterable of URLs and only yields the ones that finish with .test.*
.
Parameters
Name | Type | Description |
---|---|---|
readOnlyURLs | ReadOnlyURLs | Iterable of URLs. |
Returns
AsyncGenerator
<{ hash
: string
; host
: string
; hostname
: string
;
href
: string
; origin
: string
; password
: string
; pathname
:
string
; port
: string
; protocol
: string
; search
: string
;
searchParams
: Readonly
<ReadonlyMap
<string
, null
| string
>> ;
username
: string
; toJSON
: () => string
; toString
: () => string
},
void
, unknown
>
Example
filterTestFilePaths(["/path/to/tests/a.test.js", "/path/to/tests/b.ts"]); // ["/path/to/tests/a.test.js"]
Yields
Test URLs.
Output
FAIL
β’ Const
FAIL: string
Fail message with colors.
FAILED_TESTS
β’ Const
FAILED_TESTS: `[31m${string}[39m`
Failed test title with colors.
PASS
β’ Const
PASS: string
Pass message with colors.
TEST
β’ Const
TEST: string
Test message to be shown next to the test path.
formatValueDictionary
β’ Const
formatValueDictionary: ReadOnlyRecord
<TypeOfValue
, (value
:
unknown
) => string
>
Dictionary type->formatter to be used by formatValue
.
stringifyDifferenceDictionary
β’ Const
stringifyDifferenceDictionary: { readonly [Kind in
Difference[βkindβ]]: Function }
Dictionary Difference
kind->formatter.
formatPropertyPath
βΈ formatPropertyPath(propertyPath
): string
Stringifies and colorizes an array representing a property path.
Parameters
Name | Type | Description |
---|---|---|
propertyPath | ReadOnlyArray <PropertyKey > | Path to format. |
Returns
string
String with formatted path.
Example
formatPropertyPath(["foo", "bar"]); // "foo.bar" (with colors)
formatPropertyPath([]); // "it"
formatValue
βΈ formatValue(value
): string
Colorizes and formats a value based on its type.
Parameters
Name | Type | Description |
---|---|---|
value | unknown | Value to colorize. |
Returns
string
Colorized value as a string.
Example
formatValue(1); // "1" (with colors)
formatValue(BigInt(1)); // "1n" (with colors)
formatValue([]); // "Array([])" (with colors)
formatValue({}); // "Object({})" (with colors)
relativePath
βΈ relativePath(path
): string
Given a path, replace the CWD with a "."
to make it relative.
Parameters
Name | Type | Description |
---|---|---|
path | Object | Path to make relative. |
path.hash | string | MDN Reference |
path.host | string | MDN Reference |
path.hostname | string | MDN Reference |
path.href | string | MDN Reference |
path.origin | string | MDN Reference |
path.password | string | MDN Reference |
path.pathname | string | MDN Reference |
path.port | string | MDN Reference |
path.protocol | string | MDN Reference |
path.search | string | MDN Reference |
path.searchParams | Readonly <ReadonlyMap <string , null | string >> | MDN Reference |
path.username | string | MDN Reference |
path.toJSON | () => string | - |
path.toString | () => string | - |
Returns
string
Relative path.
Example
// If CWD is `"/projects"`
relativePath("/projects/tests/"); // "./tests/"
runAndStringifyTests
βΈ runAndStringifyTests(testTuples
): AsyncGenerator
<string
, void
,
unknown
>
Run tests in given iterable of urls and test objects and return a string with the results.
Parameters
Name | Type | Description |
---|---|---|
testTuples | IsomorphicIterable <TestTuple > | Iterable of test tuples. |
Returns
AsyncGenerator
<string
, void
, unknown
>
Example
runAndStringifyTests([
"file:///tests/example.test.ts",
{
given: "π’",
must: "π©",
received: () => "π©",
wanted: () => "π©",
},
]);
// [TEST] file:///tests/example.test.ts
// [PASS] Given π’, must π©.
Yields
Strings to be shown to the consumer.
stringifyDifference
βΈ stringifyDifference<Value
>(difference
): string
Takes a Difference
object and returns a string using
stringifyDifferenceDictionary
.
Type parameters
Name |
---|
Value |
Parameters
Name | Type | Description |
---|---|---|
difference | Difference <Value > | Difference object. |
Returns
string
Formatted string.
Example
stringifyDifference({
kind: "D",
lhs: "π’",
path: ["π’", "π©"],
}); // "π’.π© is missing."
stringifyDifference({
kind: "X",
error: "β",
}); // "there was an uncaught error: β."
stringifyTest
βΈ stringifyTest<Value
>(testResult
): string
Takes a TestResult
and returns a readable string..
Type parameters
Name |
---|
Value |
Parameters
Name | Type | Description |
---|---|---|
testResult | TestResult <Value > | Test result object. |
Returns
string
Readable string.
Example
stringifyTest({
given: "π’",
must: "π©",
}); // "[PASS] Given π’, must π©."
stringifyTest({
differences: [...],
given: "π’",
must: "π©",
}); // "[FAIL] Given π’, must π©, but..."
Test
Difference
Ζ¬ Difference<Value
>: { index
: number
; item
:
Difference
<Value
> ; kind
: "A"
; path?
:
ReadOnlyArray
<string
> } | { kind
: "D"
; lhs
: Value
; path?
:
ReadOnlyArray
<string
> } | { kind
: "E"
; lhs
: Value
; path?
:
ReadOnlyArray
<string
> ; rhs
: Value
} | { kind
: "N"
; path
:
ReadOnlyArray
<string
> ; rhs
: Value
} | { error
: unknown
; kind
:
"X"
}
Difference object from deep-diff
, with some customizations on top.
Example
const difference: Difference<string> = {
kind: "E",
path: ["π’", "π©"],
lhs: "π’",
rhs: "π©",
};
See
Type parameters
Name | Type | Description |
---|---|---|
Value | unknown | Type of value being compared. |
Differences
Ζ¬ Differences<Value
>:
ReadOnlyArray
<Difference
<Value
>>
Array of Difference
.
Example
const differences: Differences<string> = [
{
kind: "E",
path: ["π’", "π©"],
lhs: "π’",
rhs: "π©",
},
];
See
Type parameters
Name | Type | Description |
---|---|---|
Value | unknown | Type of values being compared. |
Test
Ζ¬ Test<Value
>: Object
Object that describes a test.
Example
const test: Test<number> = {
given: "a number",
must: "make it double",
received: () => double(2),
wanted: () => 4,
};
Type parameters
Name | Type | Description |
---|---|---|
Value | unknown | Type of value being tested. |
Type declaration
Name | Type | Description |
---|---|---|
given | string | Description of the given value. |
must | string | Description of the wanted value. |
received | () => Awaitable <Value > | Function that returns a value being tested. |
wanted | () => Awaitable <Value > | Functions that returns the expected value. |
TestResult
Ζ¬ TestResult<Value
>: Pick
<Test
, "given"
|
"must"
> & { differences?
:
Differences
<Value
> }
Object that describes a test result (given, must and differences).
Example
const testResult: TestResult<string> = {
given: "π’",
must: "π©",
differences: [
{
kind: "E",
path: ["π’", "π©"],
lhs: "π’",
rhs: "π©",
},
],
};
See
Type parameters
Name | Type | Description |
---|---|---|
Value | unknown | Type of value being tested. |
TestTuple
Ζ¬ TestTuple<Value
>: readonly [path: ReadOnlyURL, test: Test<Value>]
Tuple used to describe a test result and its path.
See
Type parameters
Name | Type |
---|---|
Value | unknown |
Tests
Ζ¬ Tests<Value
>:
IsomorphicIterable
<Test
<Value
>>
Iterable of Test
.
Example
const tests: Tests<number> = [
{
given: "a number",
must: "make it double",
received: () => double(2),
wanted: () => 4,
},
];
See
Type parameters
Name | Type | Description |
---|---|---|
Value | unknown | Type of value being tested. |
TestsImport
Ζ¬ TestsImport<Value
>: Promise
<ReadOnlyRecord
<PropertyKey
,
Test
<Value
> |
Tests
<Value
>>>
Promise import of a file containing Test
or Tests
.
See
Type parameters
Name | Type |
---|---|
Value | unknown |
isTest
βΈ isTest<Actual
>(value
): value is Test
Check if given value is a Test
.
Type parameters
Name | Type |
---|---|
Actual | unknown |
Parameters
Name | Type | Description |
---|---|---|
value | Test | Actual | Value to check. |
Returns
value is Test
true
if is a Test
, false
otherwise.
Example
isTest({ given: "π’", must: "π©", received: () => "π©", wanted: () => "π©" }); // true
isTest({ given: "π’", must: "π©", received: "π©", wanted: "π©" }); // false
isTest({ given: 1, must: 2, received: 3, wanted: 4 }); // false
isTest(); // false
runAndStringifyTests
βΈ runAndStringifyTests(testTuples
): AsyncGenerator
<string
, void
,
unknown
>
Run tests in given iterable of urls and test objects and return a string with the results.
Parameters
Name | Type | Description |
---|---|---|
testTuples | IsomorphicIterable <TestTuple > | Iterable of test tuples. |
Returns
AsyncGenerator
<string
, void
, unknown
>
Example
runAndStringifyTests([
"file:///tests/example.test.ts",
{
given: "π’",
must: "π©",
received: () => "π©",
wanted: () => "π©",
},
]);
// [TEST] file:///tests/example.test.ts
// [PASS] Given π’, must π©.
Yields
Strings to be shown to the consumer.
test
βΈ test<Value
>(test
):
Promise
<TestResult
<Value
>>
Takes a Test
object and returns a promise with a TestResult
.
Type parameters
Name |
---|
Value |
Parameters
Name | Type | Description |
---|---|---|
test | Test <Value > | A Test object. |
Returns
Promise
<TestResult
<Value
>>
A promise with a TestResult
object.
Example
test({
given: "π’",
must: "π©",
received: () => "π©",
wanted: () => "π©",
}); // Promise<{ given: "π’", , must: "π©" }>
test({
given: "π’",
must: "π©",
received: () => "β",
wanted: () => "π©",
}); // Promise<{ differences: [...], given: "π’", , must: "π©" }>