Test by Lou
β 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:
pnpm add -D @lou.codes/test# ornpm install -D @lou.codes/test# oryarn 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:
pnpm add -D tsx# ornpm install -D tsx# oryarn 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:
pnpm add -D c8# ornpm install -D c8# oryarn 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:
pnpm test# ornpm test# oryarn 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.
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 |
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.
Internal
EXCEPTION
β’ Const
EXCEPTION: "EXCEPTION"
Exception difference kind.
UNKNOWN_ERROR
β’ Const
UNKNOWN_ERROR: "Unknown Error"
Unknown error.
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)
stringifyDifference
βΈ stringifyDifference(difference
): string
Takes a Difference
object and returns a string using
stringifyDifferenceDictionary
.
Parameters
Name | Type | Description |
---|---|---|
difference | Difference | Difference object. |
Returns
string
Formatted string.
Example
stringifyDifference({ kind: "DELETE", left: "π’", path: ["π’", "π©"],}); // "π’.π© is missing."
stringifyDifference({ kind: "EXCEPTION", error: "β",}); // "there was an uncaught error: β."
stringifyTest
βΈ stringifyTest(testResult
): string
Takes a TestResult
and returns a readable string..
Parameters
Name | Type | Description |
---|---|---|
testResult | TestResult | 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: 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.
Differences
Ζ¬ Differences:
ReadOnlyArray
<Difference
>
Array of Difference
.
Example
const differences: Differences<string> = [ { kind: "UPDATE", left: "π’", path: ["π’", "π©"], right: "π©", },];
See
Template
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: 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.
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 | Actual | Test | Value to check. |
Returns
value is Test
true
if is a Test
, false
otherwise.
Example
isTest({ given: "π’", must: "π©", received: () => "π©", wanted: () => "π©" }); // trueisTest({ given: "π’", must: "π©", received: "π©", wanted: "π©" }); // falseisTest({ given: 1, must: 2, received: 3, wanted: 4 }); // falseisTest(); // false
test
βΈ test<Value
>(test
):
Promise
<TestResult
>
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
>
A promise with a TestResult
object.
Example
test({ given: "π’", must: "π©", received: () => "π©", wanted: () => "π©",}); // Promise<{ given: "π’", , must: "π©" }>test({ given: "π’", must: "π©", received: () => "β", wanted: () => "π©",}); // Promise<{ differences: [...], given: "π’", , must: "π©" }>