Compare commits
No commits in common. "2e844596bdad38d72f7a10701e3558faba5ce800" and "0d8f2106fff1d974ea26973f2c4bb0fb74249daf" have entirely different histories.
2e844596bd
...
0d8f2106ff
34
README.md
34
README.md
|
@ -1,34 +0,0 @@
|
||||||
# @holllo/test ✅
|
|
||||||
|
|
||||||
> **Tiny testing library designed to run anywhere.**
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import {setup} from "@holllo/test";
|
|
||||||
|
|
||||||
const add = (a: number, b: number): number => a + b;
|
|
||||||
|
|
||||||
void setup("add", async (group) => {
|
|
||||||
group.test("1 + 1", async (test) => {
|
|
||||||
test.equals(add(1, 1), 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
group.test("2 + 2", async (test) => {
|
|
||||||
test.equals(add(2, 2), 5);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
```txt
|
|
||||||
# add
|
|
||||||
- 1 + 1 passed
|
|
||||||
- 2 + 2 failed
|
|
||||||
Failed equals assertion
|
|
||||||
| Actual: 4
|
|
||||||
| Expected: 5
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Distributed under the [AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later.html) license, see [LICENSE](https://git.bauke.xyz/Holllo/test/src/branch/main/LICENSE) for more information.
|
|
14
esbuild.ts
14
esbuild.ts
|
@ -1,14 +0,0 @@
|
||||||
import {build} from "esbuild";
|
|
||||||
|
|
||||||
await build({
|
|
||||||
bundle: true,
|
|
||||||
entryPoints: ["source/index.ts"],
|
|
||||||
format: "esm",
|
|
||||||
logLevel: "info",
|
|
||||||
minify: true,
|
|
||||||
outdir: "build",
|
|
||||||
platform: "browser",
|
|
||||||
splitting: false,
|
|
||||||
target: ["es2022"],
|
|
||||||
treeShaking: true,
|
|
||||||
});
|
|
38
package.json
38
package.json
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@holllo/test",
|
|
||||||
"description": "Tiny testing library designed to run anywhere.",
|
|
||||||
"license": "AGPL-3.0-or-later",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"homepage": "https://git.bauke.xyz/Holllo/test",
|
|
||||||
"bugs": "https://github.com/Holllo/test/issues",
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"main": "./build/index.js",
|
|
||||||
"types": "./build/index.d.ts",
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"prettier": "@bauke/prettier-config",
|
|
||||||
"xo": {
|
|
||||||
"extends": "@bauke/eslint-config",
|
|
||||||
"prettier": true,
|
|
||||||
"space": true
|
|
||||||
}
|
|
||||||
}
|
|
4564
pnpm-lock.yaml
4564
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,47 +0,0 @@
|
||||||
import {type Result, Test, TestContext} from "./test.js";
|
|
||||||
|
|
||||||
/** Create a new test group and run it. */
|
|
||||||
export async function setup(
|
|
||||||
name: string,
|
|
||||||
fn: (group: Group) => Promise<void>,
|
|
||||||
): Promise<Group> {
|
|
||||||
const group = new Group(name);
|
|
||||||
await fn(group);
|
|
||||||
await group.run();
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A collection of tests. */
|
|
||||||
export class Group {
|
|
||||||
public context: TestContext = new TestContext();
|
|
||||||
public results: Result[] = [];
|
|
||||||
public tests: Test[] = [];
|
|
||||||
|
|
||||||
constructor(public name: string) {}
|
|
||||||
|
|
||||||
/** Create a new test case that doesn't get run. */
|
|
||||||
skip(name: Test["name"], fn: Test["fn"]): void {
|
|
||||||
this.tests.push(new Test(name, fn, {skip: true}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a new test case. */
|
|
||||||
test(name: Test["name"], fn: Test["fn"]): void {
|
|
||||||
this.tests.push(new Test(name, fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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)),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`# %c${this.name}`,
|
|
||||||
"font-weight: bold; text-decoration: underline;",
|
|
||||||
);
|
|
||||||
this.results = results;
|
|
||||||
for (const result of results) {
|
|
||||||
result.display();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from "./group.js";
|
|
||||||
export * from "./test.js";
|
|
130
source/test.ts
130
source/test.ts
|
@ -1,130 +0,0 @@
|
||||||
export class AssertionError extends Error {
|
|
||||||
public readonly actual: string;
|
|
||||||
public readonly expected: string;
|
|
||||||
|
|
||||||
constructor(message: string, actual: any, expected: any) {
|
|
||||||
super(message);
|
|
||||||
|
|
||||||
this.actual = JSON.stringify(actual);
|
|
||||||
this.expected = JSON.stringify(expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Test execution context with assertions. */
|
|
||||||
export class TestContext {
|
|
||||||
/** Assert strict equality with `===`. */
|
|
||||||
equals<T>(actual: T, expected: T): void {
|
|
||||||
if (actual === expected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AssertionError("Failed equals assertion", actual, expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Special options for test cases. */
|
|
||||||
export type TestOptions = {
|
|
||||||
skip?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** A test case. */
|
|
||||||
export class Test {
|
|
||||||
constructor(
|
|
||||||
public name: string,
|
|
||||||
public fn: (test: TestContext) => Promise<void>,
|
|
||||||
public options?: TestOptions,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/** Run the test and return its result. */
|
|
||||||
async run(test: TestContext): Promise<Result> {
|
|
||||||
const data: ResultData = {
|
|
||||||
duration: undefined,
|
|
||||||
error: undefined,
|
|
||||||
name: this.name,
|
|
||||||
status: "unknown",
|
|
||||||
};
|
|
||||||
|
|
||||||
const start = performance.now();
|
|
||||||
try {
|
|
||||||
if (this.options?.skip) {
|
|
||||||
data.status = "skipped";
|
|
||||||
} else {
|
|
||||||
await this.fn(test);
|
|
||||||
data.duration = performance.now() - start;
|
|
||||||
data.status = "passed";
|
|
||||||
}
|
|
||||||
} catch (_error: unknown) {
|
|
||||||
data.duration = performance.now() - start;
|
|
||||||
if (_error instanceof AssertionError) {
|
|
||||||
data.error = _error;
|
|
||||||
} else {
|
|
||||||
throw _error;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.status = "failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Result(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Data created by a test case. */
|
|
||||||
type ResultData = {
|
|
||||||
duration: number | undefined;
|
|
||||||
error: AssertionError | undefined;
|
|
||||||
name: string;
|
|
||||||
status: "failed" | "passed" | "skipped" | "unknown";
|
|
||||||
};
|
|
||||||
|
|
||||||
/** The result of a test case. */
|
|
||||||
export class Result implements ResultData {
|
|
||||||
public duration: ResultData["duration"];
|
|
||||||
public error: ResultData["error"];
|
|
||||||
public name: ResultData["name"];
|
|
||||||
public status: ResultData["status"];
|
|
||||||
|
|
||||||
constructor(data: ResultData) {
|
|
||||||
this.duration = data.duration;
|
|
||||||
this.error = data.error;
|
|
||||||
this.name = data.name;
|
|
||||||
this.status = data.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the color associated with a result status. */
|
|
||||||
statusColor(): string {
|
|
||||||
if (this.status === "failed") {
|
|
||||||
return "red";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.status === "passed") {
|
|
||||||
return "green";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.status === "skipped") {
|
|
||||||
return "yellow";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "white";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Print the result to the console. */
|
|
||||||
display(): void {
|
|
||||||
const bold = "font-weight: bold;";
|
|
||||||
const styles = [bold, `color: ${this.statusColor()}; ${bold}`];
|
|
||||||
|
|
||||||
let message = `- %c${this.name} %c${this.status}`;
|
|
||||||
if (this.duration !== undefined && this.duration > 1) {
|
|
||||||
message += ` %c${this.duration}ms`;
|
|
||||||
styles.push("color: white;");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.error !== undefined) {
|
|
||||||
message += `\n %c${this.error.message}`;
|
|
||||||
message += `\n | Actual: ${this.error.actual}`;
|
|
||||||
message += `\n | Expected: ${this.error.expected}`;
|
|
||||||
styles.push("color: pink;");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(message, ...styles);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import {setup} from "../build/index.js";
|
|
||||||
|
|
||||||
const add = (a: number, b: number): number => a + b;
|
|
||||||
|
|
||||||
void setup("add", async (group) => {
|
|
||||||
group.test("1 + 1", async (test) => {
|
|
||||||
test.equals(add(1, 1), 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
group.test("2 + 2", async (test) => {
|
|
||||||
test.equals(add(2, 2), 5);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,22 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>@holllo/test</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #222;
|
|
||||||
color: #eee;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script src="index.ts" type="module"></script>
|
|
||||||
<script src="example.ts" type="module"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,49 +0,0 @@
|
||||||
import {setup} from "../source/index.js";
|
|
||||||
|
|
||||||
async function add(a: number, b: number): Promise<number> {
|
|
||||||
await new Promise<void>((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve();
|
|
||||||
}, Math.random() * 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function subtract(a: number, b: number): Promise<number> {
|
|
||||||
await new Promise<void>((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve();
|
|
||||||
}, Math.random() * 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
return a - b;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup("add", async (group) => {
|
|
||||||
group.test("add(1, 1) = 2", async (test) => {
|
|
||||||
test.equals(await add(1, 1), 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
group.skip("add(1, 1) = 3", async (test) => {
|
|
||||||
test.equals(await add(1, 1), 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
group.test("add(1, 1) = 4", async (test) => {
|
|
||||||
test.equals(await add(1, 1), 4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
void setup("subtract", async (group) => {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": true,
|
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "ES2022",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"outDir": "build",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"target": "ES2022"
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"source"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import {defineConfig} from "vite";
|
|
||||||
|
|
||||||
const relative = (path: string) => new URL(path, import.meta.url).pathname;
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
root: relative("tests"),
|
|
||||||
});
|
|
Loading…
Reference in New Issue