Skip to content
Vangware
GitHubLinkedInYouTube

Test by Vangware

Coverage License NPM Version Open Issues

βœ… 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.

File System

ReadOnlyURL

Ζ¬ ReadOnlyURL: ReadOnly<URL>

Read-only URL.

View source


ReadOnlyURLs

Ζ¬ ReadOnlyURLs: IsomorphicIterable<ReadOnlyURL>

Iterable of ReadOnlyURLs.

See

ReadOnlyURL

View source


TestTuple

Ζ¬ TestTuple<Value>: readonly [path: ReadOnlyURL, test: Test<Value>]

Tuple used to describe a test result and its path.

See

Type parameters

NameType
Valueunknown

View source


TestsImport

Ζ¬ TestsImport<Value>: Promise<ReadOnlyRecord<PropertyKey, Test<Value> | Tests<Value>>>

Promise import of a file containing Test or Tests.

See

Type parameters

NameType
Valueunknown

View source


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

NameTypeDescription
fileOrDirectoryObjectDirectory to get files from.
fileOrDirectory.hashstringMDN Reference
fileOrDirectory.hoststringMDN Reference
fileOrDirectory.hostnamestringMDN Reference
fileOrDirectory.hrefstringMDN Reference
fileOrDirectory.originstringMDN Reference
fileOrDirectory.passwordstringMDN Reference
fileOrDirectory.pathnamestringMDN Reference
fileOrDirectory.portstringMDN Reference
fileOrDirectory.protocolstringMDN Reference
fileOrDirectory.searchstringMDN Reference
fileOrDirectory.searchParamsReadonly<ReadonlyMap<string, null | string>>MDN Reference
fileOrDirectory.usernamestringMDN 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.

View source


testImport

β–Έ testImport(path): AsyncGenerator<Test<unknown>, void, unknown>

Import a file that exports a Test or an Iterable of Test.

Parameters

NameTypeDescription
pathObjectPath to the test file.
path.hashstringMDN Reference
path.hoststringMDN Reference
path.hostnamestringMDN Reference
path.hrefstringMDN Reference
path.originstringMDN Reference
path.passwordstringMDN Reference
path.pathnamestringMDN Reference
path.portstringMDN Reference
path.protocolstringMDN Reference
path.searchstringMDN Reference
path.searchParamsReadonly<ReadonlyMap<string, null | string>>MDN Reference
path.usernamestringMDN 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.

View source


testsImport

β–Έ testsImport(urls): AsyncGenerator<TestTuple, void, unknown>

Imports all the tests of the given Iterable of urls and yields TestTuple.

Parameters

NameTypeDescription
urlsReadOnlyURLsArray 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.

View source

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

NameTypeDescription
readOnlyURLsReadOnlyURLsIterable 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.

View source

Output

FAIL

β€’ Const FAIL: string

Fail message with colors.

View source


FAILED_TESTS

β€’ Const FAILED_TESTS: `${string}`

Failed test title with colors.

View source


PASS

β€’ Const PASS: string

Pass message with colors.

View source


TEST

β€’ Const TEST: string

Test message to be shown next to the test path.

View source


formatValueDictionary

β€’ Const formatValueDictionary: ReadOnlyRecord<TypeOfValue, (value: unknown) => string>

Dictionary type->formatter to be used by formatValue.

View source


stringifyDifferenceDictionary

β€’ Const stringifyDifferenceDictionary: { readonly [Kind in Difference[β€œkind”]]: Function }

Dictionary Difference kind->formatter.

View source


formatPropertyPath

β–Έ formatPropertyPath(propertyPath): string

Stringifies and colorizes an array representing a property path.

Parameters

NameTypeDescription
propertyPathReadOnlyArray<PropertyKey>Path to format.

Returns

string

String with formatted path.

Example

formatPropertyPath(["foo", "bar"]); // "foo.bar" (with colors)
formatPropertyPath([]); // "it"

View source


formatValue

β–Έ formatValue(value): string

Colorizes and formats a value based on its type.

Parameters

NameTypeDescription
valueunknownValue 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)

View source


relativePath

β–Έ relativePath(path): string

Given a path, replace the CWD with a "." to make it relative.

Parameters

NameTypeDescription
pathObjectPath to make relative.
path.hashstringMDN Reference
path.hoststringMDN Reference
path.hostnamestringMDN Reference
path.hrefstringMDN Reference
path.originstringMDN Reference
path.passwordstringMDN Reference
path.pathnamestringMDN Reference
path.portstringMDN Reference
path.protocolstringMDN Reference
path.searchstringMDN Reference
path.searchParamsReadonly<ReadonlyMap<string, null | string>>MDN Reference
path.usernamestringMDN Reference
path.toJSON() => string-
path.toString() => string-

Returns

string

Relative path.

Example

// If CWD is `"/projects"`
relativePath("/projects/tests/"); // "./tests/"

View source


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

NameTypeDescription
testTuplesIsomorphicIterable<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.

View source


stringifyDifference

β–Έ stringifyDifference<Value>(difference): string

Takes a Difference object and returns a string using stringifyDifferenceDictionary.

Type parameters

Name
Value

Parameters

NameTypeDescription
differenceDifference<Value>Difference object.

Returns

string

Formatted string.

Example

stringifyDifference({
	kind: "D",
	lhs: "🟒",
	path: ["🟒", "🟩"],
}); // "🟒.🟩 is missing."

stringifyDifference({
	kind: "X",
	error: "❌",
}); // "there was an uncaught error: ❌."

View source


stringifyTest

β–Έ stringifyTest<Value>(testResult): string

Takes a TestResult and returns a readable string..

Type parameters

Name
Value

Parameters

NameTypeDescription
testResultTestResult<Value>Test result object.

Returns

string

Readable string.

Example

stringifyTest({
	given: "🟒",
	must: "🟩",
}); // "[PASS] Given 🟒, must 🟩."
stringifyTest({
	differences: [...],
	given: "🟒",
	must: "🟩",
}); // "[FAIL] Given 🟒, must 🟩, but..."

View source

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

deep-diff

Type parameters

NameTypeDescription
ValueunknownType of value being compared.

View source


Differences

Ζ¬ Differences<Value>: ReadOnlyArray<Difference<Value>>

Array of Difference.

Example

const differences: Differences<string> = [
	{
		kind: "E",
		path: ["🟒", "🟩"],
		lhs: "🟒",
		rhs: "🟩",
	},
];

See

Type parameters

NameTypeDescription
ValueunknownType of values being compared.

View source


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

NameTypeDescription
ValueunknownType of value being tested.

Type declaration

NameTypeDescription
givenstringDescription of the given value.
muststringDescription of the wanted value.
received() => Awaitable<Value>Function that returns a value being tested.
wanted() => Awaitable<Value>Functions that returns the expected value.

View source


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

NameTypeDescription
ValueunknownType of value being tested.

View source


TestTuple

Ζ¬ TestTuple<Value>: readonly [path: ReadOnlyURL, test: Test<Value>]

Tuple used to describe a test result and its path.

See

Type parameters

NameType
Valueunknown

View source


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

Test

Type parameters

NameTypeDescription
ValueunknownType of value being tested.

View source


TestsImport

Ζ¬ TestsImport<Value>: Promise<ReadOnlyRecord<PropertyKey, Test<Value> | Tests<Value>>>

Promise import of a file containing Test or Tests.

See

Type parameters

NameType
Valueunknown

View source


isTest

β–Έ isTest<Actual>(value): value is Test

Check if given value is a Test.

Type parameters

NameType
Actualunknown

Parameters

NameTypeDescription
valueTest | ActualValue 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

View source


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

NameTypeDescription
testTuplesIsomorphicIterable<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.

View source


test

β–Έ test<Value>(test): Promise<TestResult<Value>>

Takes a Test object and returns a promise with a TestResult.

Type parameters

Name
Value

Parameters

NameTypeDescription
testTest<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: "🟩" }>

View source