@aljazmc/testero 0.1.6 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ import type { Rules } from "./rules.js";
2
+ import type { Options } from "./testero.js";
3
+ /** Command line arguments, named like Jest's. */
4
+ export default class Arguments {
5
+ static readonly usage = "Usage: testero [patterns...] [options]\n\nRuns the test files whose paths match a pattern, every test file without patterns.\n\nOptions:\n -t, --testNamePattern <regex> run only tests whose name matches\n --bail stop after the first failing test file\n --verbose list every test\n --listTests print the test files instead of running them\n --passWithNoTests succeed when there are no test files\n --testTimeout <ms> milliseconds a test or hook may take\n -h, --help show this help\n";
6
+ readonly patterns: string[];
7
+ readonly rules: Partial<Rules>;
8
+ namePattern: RegExp | undefined;
9
+ listTests: boolean;
10
+ help: boolean;
11
+ constructor(argv: string[]);
12
+ /** The configured rules, with the ones given on the command line instead. */
13
+ options(rules: Rules): Options;
14
+ }
@@ -0,0 +1,37 @@
1
+ import { type Body, type HookKind, Suite } from "./suite.js";
2
+ type TestFunction = (name: string, body?: Body, timeout?: number) => void;
3
+ type DescribeFunction = (name: string, body: () => unknown) => void;
4
+ type Table = (table: unknown, ...values: unknown[]) => unknown;
5
+ /** `test` and `it`, with `only`, `skip`, `todo` and `each`. */
6
+ export interface TestApi extends TestFunction {
7
+ only: TestFunction & {
8
+ each: Table;
9
+ };
10
+ skip: TestFunction & {
11
+ each: Table;
12
+ };
13
+ todo: (name: string) => void;
14
+ each: Table;
15
+ }
16
+ /** `describe`, with `only`, `skip` and `each`. */
17
+ export interface DescribeApi extends DescribeFunction {
18
+ only: DescribeFunction & {
19
+ each: Table;
20
+ };
21
+ skip: DescribeFunction & {
22
+ each: Table;
23
+ };
24
+ each: Table;
25
+ }
26
+ /** Collects what a test file declares into a tree of suites. */
27
+ export default class Collector {
28
+ #private;
29
+ readonly root: Suite;
30
+ readonly describe: DescribeApi;
31
+ readonly test: TestApi;
32
+ /** Ends collection, tests cannot be declared while tests run. */
33
+ close(): void;
34
+ /** `beforeAll`, `afterAll`, `beforeEach` or `afterEach`. */
35
+ readonly hook: (kind: HookKind) => (body: Body, timeout?: number) => void;
36
+ }
37
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { Rules } from "./rules.js";
2
+ declare const _default: {
3
+ ignore: string[];
4
+ rules: Rules;
5
+ };
6
+ export default _default;
@@ -0,0 +1,11 @@
1
+ /** Rows and titles of `test.each` and `describe.each`. */
2
+ export default class Each {
3
+ #private;
4
+ /**
5
+ * The argument lists of a table: rows of arrays are spread, other rows
6
+ * are one argument, and a tagged template gives one object per row.
7
+ */
8
+ static rows(table: unknown, values: unknown[]): unknown[][];
9
+ /** A title with `$name` properties of an object row, or printf placeholders. */
10
+ static title(title: string, args: unknown[], index: number): string;
11
+ }
@@ -0,0 +1,58 @@
1
+ import { type Matcher, type MatcherContext } from "./matchers.js";
2
+ /** A failed expectation, its message says what was expected and received. */
3
+ export declare class AssertionError extends Error {
4
+ name: string;
5
+ }
6
+ type Result = void | Promise<void>;
7
+ /** The matchers an assertion has, each throwing an AssertionError when it fails. */
8
+ export interface Matchers {
9
+ toBe(expected: unknown): Result;
10
+ toEqual(expected: unknown): Result;
11
+ toStrictEqual(expected: unknown): Result;
12
+ toMatchObject(expected: object): Result;
13
+ toBeDefined(): Result;
14
+ toBeUndefined(): Result;
15
+ toBeNull(): Result;
16
+ toBeNaN(): Result;
17
+ toBeTruthy(): Result;
18
+ toBeFalsy(): Result;
19
+ toBeGreaterThan(expected: number | bigint): Result;
20
+ toBeGreaterThanOrEqual(expected: number | bigint): Result;
21
+ toBeLessThan(expected: number | bigint): Result;
22
+ toBeLessThanOrEqual(expected: number | bigint): Result;
23
+ toBeCloseTo(expected: number, digits?: number): Result;
24
+ toBeInstanceOf(expected: unknown): Result;
25
+ toContain(expected: unknown): Result;
26
+ toContainEqual(expected: unknown): Result;
27
+ toHaveLength(expected: number): Result;
28
+ toHaveProperty(path: string | PropertyKey[], value?: unknown): Result;
29
+ toMatch(expected: string | RegExp): Result;
30
+ toThrow(expected?: unknown): Result;
31
+ toHaveBeenCalled(): Result;
32
+ toHaveBeenCalledTimes(expected: number): Result;
33
+ toHaveBeenCalledWith(...expected: unknown[]): Result;
34
+ toHaveBeenLastCalledWith(...expected: unknown[]): Result;
35
+ toHaveBeenNthCalledWith(nth: number, ...expected: unknown[]): Result;
36
+ toHaveReturned(): Result;
37
+ toHaveReturnedTimes(expected: number): Result;
38
+ toHaveReturnedWith(expected: unknown): Result;
39
+ toHaveLastReturnedWith(expected: unknown): Result;
40
+ toHaveNthReturnedWith(nth: number, expected: unknown): Result;
41
+ [matcher: string]: unknown;
42
+ }
43
+ /** `expect(received)`, with every matcher, `.not`, `.resolves` and `.rejects`. */
44
+ export default class Assertion {
45
+ #private;
46
+ /** Assertions made since the last reset, for `expect.assertions(n)`. */
47
+ static count: number;
48
+ constructor(received: unknown, context?: MatcherContext);
49
+ /** The same assertion, expecting the opposite. */
50
+ get not(): Assertion & Matchers;
51
+ /** The assertion on what a promise resolves to, its matchers return promises. */
52
+ get resolves(): Assertion & Matchers;
53
+ /** The assertion on why a promise rejects, its matchers return promises. */
54
+ get rejects(): Assertion & Matchers;
55
+ /** Adds a matcher to every assertion, like `expect.extend`. */
56
+ static define(name: string, matcher: Matcher): void;
57
+ }
58
+ export {};
@@ -0,0 +1,49 @@
1
+ /** A value that matches others by a rule instead of by equality, like `expect.any(Number)`. */
2
+ export declare abstract class Asymmetric<Sample = unknown> {
3
+ protected readonly sample: Sample;
4
+ /** Set for the `expect.not` variants, which match what the rule does not. */
5
+ protected readonly inverse: boolean;
6
+ constructor(sample: Sample, inverse?: boolean);
7
+ asymmetricMatch(other: unknown): boolean;
8
+ protected abstract matches(other: unknown): boolean;
9
+ protected get prefix(): string;
10
+ }
11
+ type Constructor = abstract new (...args: never[]) => unknown;
12
+ /** `expect.any(Type)`: values of a type, primitives by their wrapper. */
13
+ export declare class Any extends Asymmetric<Constructor> {
14
+ protected matches(other: unknown): boolean;
15
+ toString(): string;
16
+ }
17
+ /** `expect.anything()`: anything but null and undefined. */
18
+ export declare class Anything extends Asymmetric<undefined> {
19
+ protected matches(other: unknown): boolean;
20
+ toString(): string;
21
+ }
22
+ /** `expect.objectContaining(object)`: objects with at least these equal properties. */
23
+ export declare class ObjectContaining extends Asymmetric<Record<PropertyKey, unknown>> {
24
+ protected matches(other: unknown): boolean;
25
+ toString(): string;
26
+ }
27
+ /** `expect.arrayContaining(array)`: arrays with these items, among others. */
28
+ export declare class ArrayContaining extends Asymmetric<unknown[]> {
29
+ protected matches(other: unknown): boolean;
30
+ toString(): string;
31
+ }
32
+ /** `expect.stringContaining(text)`: strings with this text in them. */
33
+ export declare class StringContaining extends Asymmetric<string> {
34
+ protected matches(other: unknown): boolean;
35
+ toString(): string;
36
+ }
37
+ /** `expect.stringMatching(pattern)`: strings matching a regular expression. */
38
+ export declare class StringMatching extends Asymmetric<RegExp> {
39
+ protected matches(other: unknown): boolean;
40
+ toString(): string;
41
+ }
42
+ /** `expect.closeTo(number, digits)`: numbers equal up to some decimal digits. */
43
+ export declare class CloseTo extends Asymmetric<number> {
44
+ #private;
45
+ constructor(sample: number, digits?: number, inverse?: boolean);
46
+ protected matches(other: unknown): boolean;
47
+ toString(): string;
48
+ }
49
+ export {};
@@ -0,0 +1,15 @@
1
+ /** Deep equality of `toEqual` and `toStrictEqual`, and the subsets of `toMatchObject`. */
2
+ export default class Equality {
3
+ #private;
4
+ /** Whether a value matches by its own rule, like `expect.any(Number)`. */
5
+ static asymmetric(value: unknown): value is {
6
+ asymmetricMatch(other: unknown): boolean;
7
+ };
8
+ /**
9
+ * Whether two values are deeply equal. Loosely, properties set to
10
+ * undefined count as missing; strictly, types and holes in arrays count.
11
+ */
12
+ static equals(a: unknown, b: unknown, strict?: boolean): boolean;
13
+ /** Whether received has every property expected has, recursively, like `toMatchObject`. */
14
+ static matches(received: unknown, expected: unknown): boolean;
15
+ }
@@ -0,0 +1,33 @@
1
+ import Assertion, { type Matchers } from "./assertion.js";
2
+ import { Any, Anything, ArrayContaining, CloseTo, ObjectContaining, StringContaining, StringMatching } from "./asymmetric.js";
3
+ import type { Matcher } from "./matchers.js";
4
+ type Constructor = abstract new (...args: never[]) => unknown;
5
+ /** The `expect` function, its asymmetric matchers and the assertion counts of a test. */
6
+ export default class Expect {
7
+ #private;
8
+ /** Starts the counts of a new test. */
9
+ reset(): void;
10
+ /** Failures of `expect.assertions(n)` and `expect.hasAssertions()` after a test. */
11
+ verify(): Error[];
12
+ /** The function test files call as `expect`. */
13
+ create(): ((received: unknown) => Assertion & Matchers) & {
14
+ objectContaining: (object: Record<PropertyKey, unknown>) => ObjectContaining;
15
+ arrayContaining: (array: unknown[]) => ArrayContaining;
16
+ stringContaining: (text: string) => StringContaining;
17
+ stringMatching: (pattern: string | RegExp) => StringMatching;
18
+ closeTo: (number: number, digits?: number) => CloseTo;
19
+ not: {
20
+ objectContaining: (object: Record<PropertyKey, unknown>) => ObjectContaining;
21
+ arrayContaining: (array: unknown[]) => ArrayContaining;
22
+ stringContaining: (text: string) => StringContaining;
23
+ stringMatching: (pattern: string | RegExp) => StringMatching;
24
+ closeTo: (number: number, digits?: number) => CloseTo;
25
+ };
26
+ any: (type: Constructor) => Any;
27
+ anything: () => Anything;
28
+ assertions: (count: number) => void;
29
+ hasAssertions: () => void;
30
+ extend: (added: Record<string, Matcher>) => void;
31
+ };
32
+ }
33
+ export {};
@@ -0,0 +1,10 @@
1
+ /** Prints values like Jest's pretty-format, for messages and diffs. */
2
+ export default class Format {
3
+ #private;
4
+ /** A value with one property or item per line. */
5
+ static value(value: unknown): string;
6
+ /** A value on one line. */
7
+ static inline(value: unknown): string;
8
+ /** A line diff of two values, `-` for expected lines and `+` for received ones. */
9
+ static diff(expected: unknown, received: unknown): string;
10
+ }
@@ -0,0 +1,15 @@
1
+ export interface MatcherResult {
2
+ pass: boolean;
3
+ /** The failure message, worded for `.not` too. */
4
+ message: () => string;
5
+ }
6
+ /** What a matcher knows besides its arguments. */
7
+ export interface MatcherContext {
8
+ isNot: boolean;
9
+ promise: "" | "resolves" | "rejects";
10
+ }
11
+ export type Matcher = (this: MatcherContext, received: unknown, ...expected: any[]) => MatcherResult;
12
+ /** The first line of a failure message, like `expect(received).not.toBe(expected)`. */
13
+ export declare const hint: (context: MatcherContext, name: string, expected?: string) => string;
14
+ /** Jest's matchers, `this` tells whether `.not`, `.resolves` or `.rejects` were used. */
15
+ export declare const matchers: Record<string, Matcher>;
@@ -0,0 +1,12 @@
1
+ import type Collector from "./collector.js";
2
+ import type Expect from "./expect/expect.js";
3
+ import type { Settings } from "./runner.js";
4
+ /** The functions test files use as globals, injected into them by the transformer. */
5
+ export default class Globals {
6
+ static readonly key: unique symbol;
7
+ static readonly names: string[];
8
+ /** The module the transformer injects, it reads the globals of the file being run. */
9
+ static source(): string;
10
+ /** Sets the globals for the next test file to import. */
11
+ static install(collector: Collector, expect: Expect, settings: Settings): void;
12
+ }
@@ -0,0 +1,49 @@
1
+ type Procedure = (...args: any[]) => any;
2
+ export interface MockResult {
3
+ type: "return" | "throw" | "incomplete";
4
+ value: unknown;
5
+ }
6
+ /** What a mock function remembers of its calls, as `fn.mock`. */
7
+ export interface MockState {
8
+ calls: unknown[][];
9
+ results: MockResult[];
10
+ instances: unknown[];
11
+ contexts: unknown[];
12
+ lastCall: unknown[] | undefined;
13
+ }
14
+ /** A function that records its calls, with Jest's `mock*` methods. */
15
+ export interface MockFunction<T extends Procedure = Procedure> {
16
+ (...args: Parameters<T>): ReturnType<T>;
17
+ new (...args: Parameters<T>): ReturnType<T>;
18
+ readonly mock: MockState;
19
+ mockImplementation(implementation: T): this;
20
+ mockImplementationOnce(implementation: T): this;
21
+ mockReturnValue(value: ReturnType<T>): this;
22
+ mockReturnValueOnce(value: ReturnType<T>): this;
23
+ mockResolvedValue(value: Awaited<ReturnType<T>>): this;
24
+ mockResolvedValueOnce(value: Awaited<ReturnType<T>>): this;
25
+ mockRejectedValue(value: unknown): this;
26
+ mockRejectedValueOnce(value: unknown): this;
27
+ mockReturnThis(): this;
28
+ mockName(name: string): this;
29
+ getMockName(): string;
30
+ getMockImplementation(): T | undefined;
31
+ mockClear(): this;
32
+ mockReset(): this;
33
+ mockRestore(): void;
34
+ }
35
+ /** Creates mock functions and spies, and clears, resets or restores them all at once. */
36
+ export default class Mocks {
37
+ #private;
38
+ /** A mock function, doing what an implementation does, if given. */
39
+ static fn<T extends Procedure>(implementation?: T): MockFunction<T>;
40
+ /** Replaces a method with a mock that calls it, until restored. */
41
+ static spyOn<T extends object, K extends keyof T>(object: T, method: K): MockFunction;
42
+ static isMockFunction(value: unknown): value is MockFunction;
43
+ static clearAll(): void;
44
+ static resetAll(): void;
45
+ static restoreAll(): void;
46
+ /** Forgets every mock, between test files. */
47
+ static forget(): void;
48
+ }
49
+ export {};
@@ -0,0 +1,27 @@
1
+ import type { Result } from "./runner.js";
2
+ /** The outcome of one test file. */
3
+ export interface FileResult {
4
+ /** The path of the file, relative to the root. */
5
+ file: string;
6
+ results: Result[];
7
+ /** Errors outside tests, like a file that does not compile. */
8
+ errors: unknown[];
9
+ duration: number;
10
+ }
11
+ export interface ReporterOptions {
12
+ verbose?: boolean;
13
+ color?: boolean;
14
+ write?: (text: string) => void;
15
+ }
16
+ /** Writes results like Jest's default reporter. */
17
+ export default class Reporter {
18
+ #private;
19
+ constructor(root: string, options?: ReporterOptions);
20
+ static failed(result: FileResult): boolean;
21
+ /** The results of a test file, with the failures in detail. */
22
+ file(result: FileResult): void;
23
+ /** Counts of test files and tests, and the time they took. */
24
+ summary(files: FileResult[], duration: number): void;
25
+ /** Explains that no test files were found. */
26
+ none(patterns: string[]): void;
27
+ }
@@ -0,0 +1,19 @@
1
+ /** Jest like settings, set in `testero.config.ts`. */
2
+ export interface Rules {
3
+ /** Globs of the test files in order, the last match decides, "!" excludes. */
4
+ testMatch: string[];
5
+ /** Milliseconds a test or a hook may take. */
6
+ testTimeout: number;
7
+ /** Stops after the first test file that fails. */
8
+ bail: boolean;
9
+ /** Lists every test, not only the files. */
10
+ verbose: boolean;
11
+ /** Succeeds when there are no test files. */
12
+ passWithNoTests: boolean;
13
+ /** Clears the calls of every mock before each test. */
14
+ clearMocks: boolean;
15
+ /** Also removes the implementations of every mock before each test. */
16
+ resetMocks: boolean;
17
+ /** Restores every spy to the original before each test. */
18
+ restoreMocks: boolean;
19
+ }
@@ -0,0 +1,31 @@
1
+ import { Suite, type Test } from "./suite.js";
2
+ export type Status = "passed" | "failed" | "skipped" | "todo";
3
+ export interface Result {
4
+ test: Test;
5
+ status: Status;
6
+ /** Milliseconds the test took with its hooks. */
7
+ duration: number;
8
+ errors: unknown[];
9
+ }
10
+ export interface Settings {
11
+ /** Milliseconds a test or hook may take, unless it sets its own. */
12
+ timeout: number;
13
+ /** Runs only the tests whose full name matches. */
14
+ namePattern?: RegExp | undefined;
15
+ /** Called before each test runs, like resetting mocks. */
16
+ beforeTest?: (test: Test) => void;
17
+ /** Called after each test, returns more errors, like missed assertion counts. */
18
+ afterTest?: (test: Test) => unknown[];
19
+ }
20
+ /** Runs the tests of a suite tree in order, with their hooks and timeouts. */
21
+ export default class Runner {
22
+ #private;
23
+ constructor(settings: Settings);
24
+ /** Results of every test, and errors of `afterAll` hooks that belong to no test. */
25
+ run(root: Suite): Promise<{
26
+ results: Result[];
27
+ errors: unknown[];
28
+ }>;
29
+ /** Whether a test runs: not skipped, focused when others are, matching the name pattern. */
30
+ runs(test: Test): boolean;
31
+ }
@@ -0,0 +1,41 @@
1
+ /** How a test or suite was declared: `test`, `test.only`, `test.skip` or `test.todo`. */
2
+ export type Mode = "run" | "only" | "skip" | "todo";
3
+ /** A test or hook body: it may return a promise or take a `done` callback. */
4
+ export type Body = (done: (error?: unknown) => void) => unknown;
5
+ export type HookKind = "beforeAll" | "afterAll" | "beforeEach" | "afterEach";
6
+ export interface Hook {
7
+ body: Body;
8
+ timeout: number | undefined;
9
+ }
10
+ /** A `describe` block, with its tests, nested suites and hooks in declaration order. */
11
+ export declare class Suite {
12
+ readonly name: string;
13
+ readonly mode: Mode;
14
+ readonly parent: Suite | undefined;
15
+ readonly children: (Suite | Test)[];
16
+ readonly hooks: Record<HookKind, Hook[]>;
17
+ constructor(name: string, mode?: Mode, parent?: Suite);
18
+ /** The names of the suites from the file down to this one, the file has none. */
19
+ get titles(): string[];
20
+ /** This suite and the ones around it, from the file down. */
21
+ get path(): Suite[];
22
+ /** Every test, of nested suites too, in order. */
23
+ tests(): Generator<Test>;
24
+ }
25
+ /** A named test body in a suite. */
26
+ export declare class Test {
27
+ readonly name: string;
28
+ readonly body: Body | undefined;
29
+ readonly mode: Mode;
30
+ readonly timeout: number | undefined;
31
+ readonly suite: Suite;
32
+ constructor(name: string, body: Body | undefined, mode: Mode, timeout: number | undefined, suite: Suite);
33
+ /** The names of the suites around the test, and its own. */
34
+ get titles(): string[];
35
+ /** The titles joined like Jest does for `-t`. */
36
+ get fullName(): string;
37
+ /** Whether the test, or a suite around it, is marked with `only`. */
38
+ get focused(): boolean;
39
+ /** Whether the test, or a suite around it, is skipped or todo. */
40
+ get skipped(): boolean;
41
+ }
@@ -1,4 +1,19 @@
1
- declare const _default: {
2
- greeting: string;
3
- };
4
- export default _default;
1
+ import type Config from "@aljazmc/config";
2
+ import Reporter from "./reporter.js";
3
+ import type { Rules } from "./rules.js";
4
+ /** The rules, with what the command line adds to them. */
5
+ export interface Options extends Rules {
6
+ /** Runs only test files whose path matches one of these regular expressions. */
7
+ patterns: string[];
8
+ /** Runs only tests whose full name matches. */
9
+ namePattern?: RegExp | undefined;
10
+ }
11
+ /** Finds test files, then compiles, runs and reports them one after another. */
12
+ export default class Testero {
13
+ #private;
14
+ constructor(config: Config<Rules>, options: Options, reporter?: Reporter);
15
+ /** The test files to run, relative to the root. */
16
+ files(): string[];
17
+ /** Runs every test file, returns whether all of them passed. */
18
+ run(): Promise<boolean>;
19
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Compiles a test file and the local files it imports with esbuild, into one
3
+ * module Node can import. Installed packages stay external, and the globals
4
+ * like `describe` and `expect` are injected where the file uses them.
5
+ */
6
+ export default class Transformer {
7
+ #private;
8
+ /** Packages test files import the globals from, like with Jest. */
9
+ static readonly modules: string[];
10
+ /** Where compiled test files go, stack frames there are testero's own. */
11
+ static readonly prefix: string;
12
+ constructor();
13
+ /** Bundles a test file, returns the URL of the module to import. */
14
+ compile(file: string): Promise<string>;
15
+ /** Removes the compiled files. */
16
+ dispose(): void;
17
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aljazmc/testero",
3
- "version": "0.1.6",
4
- "description": "A simple test runner. Easy to understand, easy to customize and easy to extend.",
3
+ "version": "0.2.1",
4
+ "description": "A simple, Jest like test runner. Easy to understand, easy to customize and easy to extend.",
5
5
  "author": "aljazmc",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -10,19 +10,17 @@
10
10
  "url": "https://github.com/aljazmc/aljazmc.git",
11
11
  "directory": "packages/testero"
12
12
  },
13
- "devDependencies": {
14
- "@types/node": "^25.6.0"
15
- },
16
13
  "keywords": [
17
14
  "customizable",
15
+ "jest",
16
+ "test runner",
18
17
  "tester",
19
18
  "typescript",
20
19
  "modern",
21
20
  "modular"
22
21
  ],
23
- "packageManager": "yarn@4.13.0",
24
22
  "engines": {
25
- "node": "^20.19.0 || ^22.13.0 || >=24"
23
+ "node": "^20.19.0 || ^22.13.0 || ^24 || ^25 || ^26"
26
24
  },
27
25
  "types": "./bin/types/testero.d.ts",
28
26
  "bin": "./bin/main.js",
@@ -32,5 +30,13 @@
32
30
  "default": "./bin/main.js"
33
31
  },
34
32
  "./package.json": "./package.json"
33
+ },
34
+ "devDependencies": {
35
+ "@aljazmc/config": "0.2.0",
36
+ "@jest/globals": "^30.5.2",
37
+ "@types/node": "^26.6.5"
38
+ },
39
+ "dependencies": {
40
+ "esbuild": "0.28.2"
35
41
  }
36
42
  }