Skip to content

Test by Lou

Coverage License NPM Version Open Issues Size

βœ… Equality test with enforced readability, based on the concept of RITEway and inspired by uvu.

Usage

πŸ“¦ Node

Install @lou.codes/tests as a dev dependency:

Terminal window
pnpm add -D @lou.codes/test
# or
npm install -D @lou.codes/test
# or
yarn add --dev @lou.codes/test

Add a test script to package.json:

{
"scripts": {
"test": "test"
}
}
Add TypeScript support

To support TypeScript, install tsx as a dev dependency:

Terminal window
pnpm add -D tsx
# or
npm install -D tsx
# or
yarn add --dev tsx

And update package.json:

{
"scripts": {
"test": "NODE_OPTIONS='--import tsx' test"
}
}
Add coverage

To add coverage, install c8 as a dev dependency:

Terminal window
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='--import tsx' c8 test"
}
}

Run tests:

Terminal window
pnpm test
# or
npm test
# or
yarn test

πŸ¦• Deno

Import @lou.codes/test using the npm: prefix, and use it directly:

import { test } from "npm:@lou.codes/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 @lou.codes/test using esm.sh, and use it directly:

<script type="module">
import { test } from "https://esm.sh/@lou.codes/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);
</script>

Writing tests

TypeScript

import type { Tests } from "@lou.codes/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("@lou.codes/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 "@lou.codes/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 "@lou.codes/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 "@lou.codes/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

@lou.codes/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


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

Internal

EXCEPTION

β€’ Const EXCEPTION: "EXCEPTION"

Exception difference kind.

View source


UNKNOWN_ERROR

β€’ Const UNKNOWN_ERROR: "Unknown Error"

Unknown error.

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


stringifyDifference

β–Έ stringifyDifference(difference): string

Takes a Difference object and returns a string using stringifyDifferenceDictionary.

Parameters

NameTypeDescription
differenceDifferenceDifference object.

Returns

string

Formatted string.

Example

stringifyDifference({
kind: "DELETE",
left: "🟒",
path: ["🟒", "🟩"],
}); // "🟒.🟩 is missing."
stringifyDifference({
kind: "EXCEPTION",
error: "❌",
}); // "there was an uncaught error: ❌."

View source


stringifyTest

β–Έ stringifyTest(testResult): string

Takes a TestResult and returns a readable string..

Parameters

NameTypeDescription
testResultTestResultTest 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: CreateDifference | DeleteDifference | UpdateDifference | { error: unknown ; kind: typeof EXCEPTION }

Difference object from @lou.codes/diff, with an added β€œEXCEPTION” kind.

Example

const difference: Difference = {
kind: "UPDATE",
left: "🟒",
path: ["🟒", "🟩"],
right: "🟩",
};

See

Template

Type of value being compared.

View source


Differences

Ζ¬ Differences: ReadOnlyArray<Difference>

Array of Difference.

Example

const differences: Differences<string> = [
{
kind: "UPDATE",
left: "🟒",
path: ["🟒", "🟩"],
right: "🟩",
},
];

See

Template

Type 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: Pick<Test, "given" | "must"> & { differences?: Differences }

Object that describes a test result (given, must and differences).

Example

const testResult: TestResult<string> = {
given: "🟒",
must: "🟩",
differences: [
{
kind: "UPDATE",
path: ["🟒", "🟩"],
left: "🟒",
right: "🟩",
},
],
};

See

Template

Type 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
valueActual | TestValue 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


test

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

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>

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