Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
Bauke | 30fc15fb8d | |
Bauke | 82d68f8066 | |
Bauke | 967c709b85 | |
Bauke | 50d5b71d03 | |
Bauke | 373d85a17b | |
Bauke | 25547cbbbb | |
Bauke | 355efe1233 | |
Bauke | a0ee7b94c2 | |
Bauke | 85b835eba1 | |
Bauke | a0d46f7018 | |
Bauke | 035cac86cc | |
Bauke | 40dc13b644 | |
Bauke | cfd05d028e | |
Bauke | ec714638a4 | |
Bauke | d8f64e1de6 |
|
@ -1,2 +1,3 @@
|
|||
.direnv/
|
||||
build/
|
||||
node_modules/
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# 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"]
|
|
@ -9,7 +9,7 @@ import {setup} from "@holllo/test";
|
|||
|
||||
const add = (a: number, b: number): number => a + b;
|
||||
|
||||
void setup("add", async (group) => {
|
||||
await setup("add", async (group) => {
|
||||
group.test("1 + 1", async (test) => {
|
||||
test.equals(add(1, 1), 2);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"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
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
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; };
|
||||
}
|
||||
);
|
||||
}
|
24
package.json
24
package.json
|
@ -2,9 +2,8 @@
|
|||
"name": "@holllo/test",
|
||||
"description": "Tiny testing library designed to run anywhere.",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.2",
|
||||
"homepage": "https://git.bauke.xyz/Holllo/test",
|
||||
"bugs": "https://github.com/Holllo/test/issues",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
@ -14,20 +13,15 @@
|
|||
"files": [
|
||||
"build/"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsx esbuild.ts && tsc",
|
||||
"dev": "vite",
|
||||
"lint": "xo",
|
||||
"test": "pnpm run build && tsx tests/index.ts && tsx tests/example.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bauke/eslint-config": "^0.1.2",
|
||||
"@bauke/prettier-config": "^0.1.2",
|
||||
"esbuild": "^0.16.10",
|
||||
"tsx": "^3.12.1",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.2",
|
||||
"xo": "^0.53.1"
|
||||
"@bauke/eslint-config": "^0.1.5",
|
||||
"@bauke/prettier-config": "^0.1.5",
|
||||
"esbuild": "^0.19.12",
|
||||
"trash-cli": "^5.0.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.12",
|
||||
"xo": "^0.56.0"
|
||||
},
|
||||
"prettier": "@bauke/prettier-config",
|
||||
"xo": {
|
||||
|
|
4513
pnpm-lock.yaml
4513
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
{ pkgs ? import <nixpkgs> { } }:
|
||||
|
||||
with pkgs;
|
||||
|
||||
mkShell rec {
|
||||
packages = [ cargo-make nodejs nodePackages.pnpm ];
|
||||
}
|
|
@ -4,20 +4,45 @@ import {type Result, Test, TestContext} from "./test.js";
|
|||
export async function setup(
|
||||
name: string,
|
||||
fn: (group: Group) => Promise<void>,
|
||||
options?: GroupOptions,
|
||||
): Promise<Group> {
|
||||
const group = new Group(name);
|
||||
const group = new Group(name, options);
|
||||
await fn(group);
|
||||
await group.run();
|
||||
return group;
|
||||
}
|
||||
|
||||
/** Options for test groups. */
|
||||
export type GroupOptions = {
|
||||
parallel?: boolean;
|
||||
};
|
||||
|
||||
/** A collection of tests. */
|
||||
export class Group {
|
||||
export class Group implements GroupOptions {
|
||||
public context: TestContext = new TestContext();
|
||||
public parallel: boolean;
|
||||
public results: Result[] = [];
|
||||
public tests: Test[] = [];
|
||||
|
||||
constructor(public name: string) {}
|
||||
private _afterAll: (() => Promise<void>) | undefined;
|
||||
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. */
|
||||
skip(name: Test["name"], fn: Test["fn"]): void {
|
||||
|
@ -31,9 +56,25 @@ export class Group {
|
|||
|
||||
/** Run all the tests from this group and display their results. */
|
||||
async run(): Promise<void> {
|
||||
const results = await Promise.all(
|
||||
this.tests.map(async (test) => test.run(this.context)),
|
||||
);
|
||||
if (this._beforeAll !== undefined) {
|
||||
await this._beforeAll();
|
||||
}
|
||||
|
||||
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(
|
||||
`# %c${this.name}`,
|
||||
|
|
|
@ -1,24 +1,51 @@
|
|||
export class AssertionError extends Error {
|
||||
public readonly actual: string;
|
||||
public readonly expected: string;
|
||||
public readonly title: string | undefined;
|
||||
|
||||
constructor(message: string, actual: any, expected: any) {
|
||||
constructor(message: string, actual: any, expected: any, title?: string) {
|
||||
super(message);
|
||||
|
||||
this.actual = JSON.stringify(actual);
|
||||
this.expected = JSON.stringify(expected);
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
/** Test execution context with assertions. */
|
||||
export class TestContext {
|
||||
/** Assert strict equality with `===`. */
|
||||
equals<T>(actual: T, expected: T): void {
|
||||
equals<T>(actual: T, expected: T, title?: string): void {
|
||||
if (actual === expected) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AssertionError("Failed equals assertion", actual, expected);
|
||||
throw new AssertionError(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,6 +147,7 @@ export class Result implements ResultData {
|
|||
|
||||
if (this.error !== undefined) {
|
||||
message += `\n %c${this.error.message}`;
|
||||
message += this.error.title === undefined ? "" : `: ${this.error.title}`;
|
||||
message += `\n | Actual: ${this.error.actual}`;
|
||||
message += `\n | Expected: ${this.error.expected}`;
|
||||
styles.push("color: pink;");
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
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");
|
||||
});
|
||||
});
|
|
@ -1,8 +1,8 @@
|
|||
import {setup} from "../build/index.js";
|
||||
import {setup} from "../source/index.js";
|
||||
|
||||
const add = (a: number, b: number): number => a + b;
|
||||
|
||||
void setup("add", async (group) => {
|
||||
await setup("Example add", async (group) => {
|
||||
group.test("1 + 1", async (test) => {
|
||||
test.equals(add(1, 1), 2);
|
||||
});
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
<body>
|
||||
<script src="index.ts" type="module"></script>
|
||||
<script src="example.ts" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import {setup} from "../source/index.js";
|
||||
|
||||
await import("./assertions.js");
|
||||
await import("./example.js");
|
||||
|
||||
async function add(a: number, b: number): Promise<number> {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
|
@ -20,7 +23,7 @@ async function subtract(a: number, b: number): Promise<number> {
|
|||
return a - b;
|
||||
}
|
||||
|
||||
void setup("add", async (group) => {
|
||||
await setup("add", async (group) => {
|
||||
group.test("add(1, 1) = 2", async (test) => {
|
||||
test.equals(await add(1, 1), 2);
|
||||
});
|
||||
|
@ -34,16 +37,30 @@ void setup("add", async (group) => {
|
|||
});
|
||||
});
|
||||
|
||||
void setup("subtract", async (group) => {
|
||||
group.test("subtract(1, 1) = 0", async (test) => {
|
||||
test.equals(await subtract(1, 1), 0);
|
||||
});
|
||||
await setup(
|
||||
"subtract",
|
||||
async (group) => {
|
||||
group.beforeAll(async () => {
|
||||
console.log("subtract beforeAll hook");
|
||||
});
|
||||
|
||||
group.skip("subtract(1, 1) = 1", async (test) => {
|
||||
test.equals(await subtract(1, 1), 1);
|
||||
});
|
||||
group.afterAll(async () => {
|
||||
console.log("subtract afterAll hook");
|
||||
});
|
||||
|
||||
group.test("subtract(1, 1) = 2", async (test) => {
|
||||
test.equals(await subtract(1, 1), 2);
|
||||
});
|
||||
});
|
||||
group.test("subtract(1, 1) = 0", async (test) => {
|
||||
test.equals(await subtract(1, 1), 0);
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue