Compare commits

..

No commits in common. "main" and "0.1.0" have entirely different histories.
main ... 0.1.0

15 changed files with 1878 additions and 2933 deletions

3
.envrc
View File

@ -1,3 +0,0 @@
#!/usr/bin/env bash
use flake

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
.direnv/
build/ build/
node_modules/ node_modules/

View File

@ -1,45 +0,0 @@
# Build the package.
[tasks.build]
clear = true
dependencies = ["clean", "lint", "test", "build-types", "build-js"]
# Build the JavaScript.
[tasks.build-js]
clear = true
command = "pnpm"
args = ["tsx", "esbuild.ts"]
# Build the TypeScript type declarations.
[tasks.build-types]
clear = true
command = "pnpm"
args = ["tsc"]
# Clean the build directory.
[tasks.clean]
clear = true
command = "pnpm"
args = ["trash", "build/"]
# Run the full set of linting and testing.
[tasks.lint]
clear = true
dependencies = ["lint-js", "test"]
# Lint the TypeScript using XO.
[tasks.lint-js]
clear = true
command = "pnpm"
args = ["xo"]
# Run the tests.
[tasks.test]
clear = true
command = "pnpm"
args = ["tsx", "tests/index.ts"]
# Start a live-reloading server that watches for changes.
[tasks.watch]
clear = true
command = "pnpm"
args = ["vite"]

View File

@ -9,7 +9,7 @@ import {setup} from "@holllo/test";
const add = (a: number, b: number): number => a + b; const add = (a: number, b: number): number => a + b;
await setup("add", async (group) => { void setup("add", async (group) => {
group.test("1 + 1", async (test) => { group.test("1 + 1", async (test) => {
test.equals(add(1, 1), 2); test.equals(add(1, 1), 2);
}); });

View File

@ -1,59 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1706173671,
"narHash": "sha256-lciR7kQUK2FCAYuszyd7zyRRmTaXVeoZsCyK6QFpGdk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4fddc9be4eaf195d631333908f2a454b03628ee5",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,13 +0,0 @@
{
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = import ./shell.nix { inherit pkgs; };
}
);
}

View File

@ -2,8 +2,9 @@
"name": "@holllo/test", "name": "@holllo/test",
"description": "Tiny testing library designed to run anywhere.", "description": "Tiny testing library designed to run anywhere.",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"version": "0.2.2", "version": "0.1.0",
"homepage": "https://git.bauke.xyz/Holllo/test", "homepage": "https://git.bauke.xyz/Holllo/test",
"bugs": "https://github.com/Holllo/test/issues",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@ -13,15 +14,20 @@
"files": [ "files": [
"build/" "build/"
], ],
"scripts": {
"build": "tsx esbuild.ts && tsc",
"dev": "vite",
"lint": "xo",
"test": "pnpm run build && tsx tests/index.ts && tsx tests/example.ts"
},
"devDependencies": { "devDependencies": {
"@bauke/eslint-config": "^0.1.5", "@bauke/eslint-config": "^0.1.2",
"@bauke/prettier-config": "^0.1.5", "@bauke/prettier-config": "^0.1.2",
"esbuild": "^0.19.12", "esbuild": "^0.16.10",
"trash-cli": "^5.0.0", "tsx": "^3.12.1",
"tsx": "^4.7.0", "typescript": "^4.9.4",
"typescript": "^5.3.3", "vite": "^4.0.2",
"vite": "^5.0.12", "xo": "^0.53.1"
"xo": "^0.56.0"
}, },
"prettier": "@bauke/prettier-config", "prettier": "@bauke/prettier-config",
"xo": { "xo": {

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell rec {
packages = [ cargo-make nodejs nodePackages.pnpm ];
}

View File

@ -4,45 +4,20 @@ import {type Result, Test, TestContext} from "./test.js";
export async function setup( export async function setup(
name: string, name: string,
fn: (group: Group) => Promise<void>, fn: (group: Group) => Promise<void>,
options?: GroupOptions,
): Promise<Group> { ): Promise<Group> {
const group = new Group(name, options); const group = new Group(name);
await fn(group); await fn(group);
await group.run(); await group.run();
return group; return group;
} }
/** Options for test groups. */
export type GroupOptions = {
parallel?: boolean;
};
/** A collection of tests. */ /** A collection of tests. */
export class Group implements GroupOptions { export class Group {
public context: TestContext = new TestContext(); public context: TestContext = new TestContext();
public parallel: boolean;
public results: Result[] = []; public results: Result[] = [];
public tests: Test[] = []; public tests: Test[] = [];
private _afterAll: (() => Promise<void>) | undefined; constructor(public name: string) {}
private _beforeAll: (() => Promise<void>) | undefined;
constructor(
public name: string,
options?: GroupOptions,
) {
this.parallel = options?.parallel ?? false;
}
/** Set a function to run after all tests have finished. */
afterAll(fn: Group["_afterAll"]): void {
this._afterAll = fn;
}
/** Set a function to run before all tests begin. */
beforeAll(fn: Group["_beforeAll"]): void {
this._beforeAll = fn;
}
/** Create a new test case that doesn't get run. */ /** Create a new test case that doesn't get run. */
skip(name: Test["name"], fn: Test["fn"]): void { skip(name: Test["name"], fn: Test["fn"]): void {
@ -56,25 +31,9 @@ export class Group implements GroupOptions {
/** Run all the tests from this group and display their results. */ /** Run all the tests from this group and display their results. */
async run(): Promise<void> { async run(): Promise<void> {
if (this._beforeAll !== undefined) { const results = await Promise.all(
await this._beforeAll(); this.tests.map(async (test) => test.run(this.context)),
} );
let results: Result[];
if (this.parallel) {
results = await Promise.all(
this.tests.map(async (test) => test.run(this.context)),
);
} else {
results = [];
for (const test of this.tests) {
results.push(await test.run(this.context));
}
}
if (this._afterAll !== undefined) {
await this._afterAll();
}
console.log( console.log(
`# %c${this.name}`, `# %c${this.name}`,

View File

@ -1,51 +1,24 @@
export class AssertionError extends Error { export class AssertionError extends Error {
public readonly actual: string; public readonly actual: string;
public readonly expected: string; public readonly expected: string;
public readonly title: string | undefined;
constructor(message: string, actual: any, expected: any, title?: string) { constructor(message: string, actual: any, expected: any) {
super(message); super(message);
this.actual = JSON.stringify(actual); this.actual = JSON.stringify(actual);
this.expected = JSON.stringify(expected); this.expected = JSON.stringify(expected);
this.title = title;
} }
} }
/** Test execution context with assertions. */ /** Test execution context with assertions. */
export class TestContext { export class TestContext {
/** Assert strict equality with `===`. */ /** Assert strict equality with `===`. */
equals<T>(actual: T, expected: T, title?: string): void { equals<T>(actual: T, expected: T): void {
if (actual === expected) { if (actual === expected) {
return; return;
} }
throw new AssertionError( throw new AssertionError("Failed equals assertion", actual, expected);
"Failed equals assertion",
actual,
expected,
title,
);
}
/** Assert that the value is false. */
false(actual: boolean, title?: string): void {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
if (actual === false) {
return;
}
throw new AssertionError("Failed false assertion", actual, false, title);
}
/** Assert that the value is true. */
true(actual: boolean, title?: string): void {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
if (actual === true) {
return;
}
throw new AssertionError("Failed true assertion", actual, true, title);
} }
} }
@ -147,7 +120,6 @@ export class Result implements ResultData {
if (this.error !== undefined) { if (this.error !== undefined) {
message += `\n %c${this.error.message}`; message += `\n %c${this.error.message}`;
message += this.error.title === undefined ? "" : `: ${this.error.title}`;
message += `\n | Actual: ${this.error.actual}`; message += `\n | Actual: ${this.error.actual}`;
message += `\n | Expected: ${this.error.expected}`; message += `\n | Expected: ${this.error.expected}`;
styles.push("color: pink;"); styles.push("color: pink;");

View File

@ -1,19 +0,0 @@
import {setup} from "../source/index.js";
await setup("Assertions", async (group) => {
group.test("equals", async (test) => {
test.equals(true, true, "boolean");
test.equals(Math.PI, Math.PI, "number");
test.equals("A string!", "A string!", "string");
});
group.test("false", async (test) => {
test.false(1 < 0, "logic");
test.false(new Date() instanceof String, "instanceof");
});
group.test("true", async (test) => {
test.true(1 > 0, "logic");
test.true(new Date() instanceof Date, "instanceof");
});
});

View File

@ -1,8 +1,8 @@
import {setup} from "../source/index.js"; import {setup} from "../build/index.js";
const add = (a: number, b: number): number => a + b; const add = (a: number, b: number): number => a + b;
await setup("Example add", async (group) => { void setup("add", async (group) => {
group.test("1 + 1", async (test) => { group.test("1 + 1", async (test) => {
test.equals(add(1, 1), 2); test.equals(add(1, 1), 2);
}); });

View File

@ -16,6 +16,7 @@
<body> <body>
<script src="index.ts" type="module"></script> <script src="index.ts" type="module"></script>
<script src="example.ts" type="module"></script>
</body> </body>
</html> </html>

View File

@ -1,8 +1,5 @@
import {setup} from "../source/index.js"; import {setup} from "../source/index.js";
await import("./assertions.js");
await import("./example.js");
async function add(a: number, b: number): Promise<number> { async function add(a: number, b: number): Promise<number> {
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(() => { setTimeout(() => {
@ -23,7 +20,7 @@ async function subtract(a: number, b: number): Promise<number> {
return a - b; return a - b;
} }
await setup("add", async (group) => { void setup("add", async (group) => {
group.test("add(1, 1) = 2", async (test) => { group.test("add(1, 1) = 2", async (test) => {
test.equals(await add(1, 1), 2); test.equals(await add(1, 1), 2);
}); });
@ -37,30 +34,16 @@ await setup("add", async (group) => {
}); });
}); });
await setup( void setup("subtract", async (group) => {
"subtract", group.test("subtract(1, 1) = 0", async (test) => {
async (group) => { test.equals(await subtract(1, 1), 0);
group.beforeAll(async () => { });
console.log("subtract beforeAll hook");
});
group.afterAll(async () => { group.skip("subtract(1, 1) = 1", async (test) => {
console.log("subtract afterAll hook"); test.equals(await subtract(1, 1), 1);
}); });
group.test("subtract(1, 1) = 0", async (test) => { group.test("subtract(1, 1) = 2", async (test) => {
test.equals(await subtract(1, 1), 0); test.equals(await subtract(1, 1), 2);
}); });
});
group.skip("subtract(1, 1) = 1", async (test) => {
test.equals(await subtract(1, 1), 1);
});
group.test("subtract(1, 1) = 2", async (test) => {
test.equals(await subtract(1, 1), 2, "extra title");
});
},
{
parallel: false,
},
);