Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module.exports = {
extends: ['@exodus/eslint-config/typescript'],
rules: {
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'unicorn/consistent-function-scoping': 'off',
},
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*~
node_modules
package-lock.json
coverage
playground
*.tgz
32 changes: 27 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,28 @@
"exports": {
"./node-test-reporter": "./bin/reporter.js",
"./loader/jest": "./loader/jest.js",
"./benchmark": "./src/benchmark.js",
"./expect": "./src/expect.cjs",
"./jest": "./src/jest.js",
"./mock": "./src/mock.js",
"./node": "./src/node.js",
"./benchmark": {
"types": "./src/benchmark.d.ts",
"default": "./src/benchmark.js"
},
"./expect": {
"types": "./src/expect.d.ts",
"default": "./src/expect.cjs"
},
"./jest": {
"types": "./src/jest.d.ts",
"default": "./src/jest.js"
},
"./mock": {
"types": "./src/mock.d.ts",
"default": "./src/mock.js"
},
"./node": {
"types": "./src/node.d.ts",
"default": "./src/node.js"
},
"./tape": {
"types": "./src/tape.d.ts",
"import": "./src/tape.js",
"require": "./src/tape.cjs"
}
Expand All @@ -57,6 +73,7 @@
"loader/typescript.js",
"loader/typescript.loader.js",
"src/benchmark.js",
"src/benchmark.d.ts",
"src/dark.cjs",
"src/engine.js",
"src/engine.node.cjs",
Expand All @@ -65,8 +82,10 @@
"src/engine.select.cjs",
"src/exodus.js",
"src/expect.cjs",
"src/expect.d.ts",
"src/glob.cjs",
"src/jest.js",
"src/jest.d.ts",
"src/jest.config.js",
"src/jest.config.fs.js",
"src/jest.environment.js",
Expand All @@ -76,10 +95,13 @@
"src/jest.snapshot.js",
"src/jest.timers.js",
"src/mock.js",
"src/mock.d.ts",
"src/node.js",
"src/node.d.ts",
"src/pretty-format.cjs",
"src/replay.js",
"src/tape.js",
"src/tape.d.ts",
"src/tape.cjs",
"src/timers-track.js",
"src/version.js",
Expand Down
37 changes: 37 additions & 0 deletions src/benchmark.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Type definitions for @exodus/test/benchmark
* Provides benchmark utilities for performance testing
*/

/// <reference types="node" />

/**
* Benchmark options
*/
export interface BenchmarkOptions {
/** Array of arguments to pass to the benchmark function */
args?: any[]

/** Timeout in milliseconds (default: 1000) */
timeout?: number

/** Skip this benchmark */
skip?: boolean
}

/**
* Runs a benchmark
* @param name - Name of the benchmark
* @param options - Benchmark options
* @param fn - Function to benchmark
*/
export declare function benchmark(
name: string,
options: BenchmarkOptions,
fn: (arg?: any) => any | Promise<any>
): Promise<void>
Comment on lines +11 to +32
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make this generic over args for type checking of fn against args (or vice versa)

Suggested change
export interface BenchmarkOptions {
/** Array of arguments to pass to the benchmark function */
args?: any[]
/** Timeout in milliseconds (default: 1000) */
timeout?: number
/** Skip this benchmark */
skip?: boolean
}
/**
* Runs a benchmark
* @param name - Name of the benchmark
* @param options - Benchmark options
* @param fn - Function to benchmark
*/
export declare function benchmark(
name: string,
options: BenchmarkOptions,
fn: (arg?: any) => any | Promise<any>
): Promise<void>
export interface BenchmarkOptions<A> {
/** Array of arguments to pass to the benchmark function */
args?: A[]
/** Timeout in milliseconds (default: 1000) */
timeout?: number
/** Skip this benchmark */
skip?: boolean
}
/**
* Runs a benchmark
* @param name - Name of the benchmark
* @param options - Benchmark options
* @param fn - Function to benchmark
*/
export declare function benchmark<A>(
name: string,
options: BenchmarkOptions<A>,
fn: (arg?: A) => any | Promise<any>
): Promise<void>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked the implementation: arg passed to fn is not optional, right? we pass the call count if no args are provided, so we should also accommodate for that in the type:

test/src/benchmark.js

Lines 42 to 45 in b6a9857

const arg = args ? args[count % args.length] : count
count++
const start = getTime()
const val = fn(arg)

Copy link
Copy Markdown
Collaborator

@ChALkeR ChALkeR Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function can ignore the passed argument
And in many benchmarks, there are no arguments expected by the function

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function can ignore the passed argument

it can but that is the point. Functions are contravariant. The function that you provide can accept more general or less parameters than the caller is expected to provide. I.e. you can provide a function that receives no arguments where a function that accepts one parameter is expected.

That also means in our function signature arg should not be optional (marked with a ?). The caller can still pass in a function that accepts no arg based on the contravariance rules

Copy link
Copy Markdown
Collaborator

@ChALkeR ChALkeR Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sparten11740 I will include the types proposed above, thanks!


export declare function benchmark(
name: string,
fn: (arg?: any) => any | Promise<any>
): Promise<void>
10 changes: 10 additions & 0 deletions src/expect.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Type definitions for @exodus/test/expect
* Re-exports types from the expect package
*/
Comment thread
ChALkeR marked this conversation as resolved.

export type { Expect } from 'expect'
export declare const expect: import('expect').Expect

/** Load the expect library (for internal use) */
export declare function loadExpect(reason?: string): import('expect').Expect
18 changes: 18 additions & 0 deletions src/jest.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Type definitions for @exodus/test/jest
* Re-exports types from @jest/globals where possible
*/

import type { Expect } from './expect.d.ts'

// Re-export Jest types from @jest/globals
export { describe, test, it, beforeEach, afterEach, beforeAll, afterAll } from '@jest/globals'

// Re-export jest object
export declare const jest: typeof import('@jest/globals').jest

/** Alias for test() */
export declare function should(name: string, fn: () => void | Promise<void>, timeout?: number): void

export type { Expect } from './expect.d.ts'
export declare const expect: Expect
49 changes: 49 additions & 0 deletions src/mock.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Type definitions for @exodus/test/mock
* Provides mocking and testing utilities
*/
Comment thread
ChALkeR marked this conversation as resolved.

/// <reference types="node" />

/**
* Network replay utilities
*/

/** Records fetch calls and returns a fetch function */
export declare function fetchRecord(options?: any): typeof fetch

/** Replays fetch calls from recording and returns a fetch function */
export declare function fetchReplay(): typeof fetch

/** Records WebSocket calls and returns a WebSocket constructor */
export declare function websocketRecord(options?: any): typeof WebSocket

/** Replays WebSocket calls from recording and returns a WebSocket constructor */
export declare function websocketReplay(options?: any): typeof WebSocket

/**
* Timer tracking and debugging utilities
*/

/** Enables timer tracking */
export declare function timersTrack(): void

/** Outputs debug information about active timers */
export declare function timersDebug(): void

/** Lists all active timers */
export declare function timersList(): any[]

/** Asserts no timers are active */
export declare function timersAssert(): void

/**
* Speeds up timers by the given rate
* @param rate - Speed multiplier (e.g., 2 means 2x faster)
* @param options - Configuration options
* @param options.apis - Array of APIs to speed up (default: ['setTimeout', 'setInterval', 'Date'])
*/
export declare function timersSpeedup(
rate: number,
options?: { apis?: ('setTimeout' | 'setInterval' | 'Date')[] }
): void
20 changes: 20 additions & 0 deletions src/node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Type definitions for @exodus/test/node
* Re-exports types from @types/node where possible
*/

/// <reference types="node" />

// Re-export from node:test module
export { mock, describe, test, it, beforeEach, afterEach, before, after } from 'node:test'

/**
* Snapshot utilities
*/
export declare const snapshot: {
/** Sets default snapshot serializers */
setDefaultSnapshotSerializers(serializers: any[]): void
Copy link
Copy Markdown
Contributor

@sparten11740 sparten11740 Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we improve on this? any type is no type. I am assuming a serializer is a function that accepts some args (what args?) and returns a string?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 229d4bd.


/** Sets the snapshot path resolver (not supported) */
setResolveSnapshotPath(): never
}
156 changes: 156 additions & 0 deletions src/tape.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* Type definitions for @exodus/test/tape
* Provides tape-compatible testing APIs
*/

/// <reference types="node" />

/**
* Tape test context/assertion object
*/
export interface Test {
/** Plans the number of assertions */
plan(count: number): void

/** Ends the test */
end(): void

/** Skips the test */
skip(msg?: string): void

/** Marks test as todo */
todo(msg?: string): void

/** Adds a comment */
comment(msg: string): void

/** Nested test */
test(name: string, cb: (t: Test) => void): void
test(name: string, opts: TestOptions, cb: (t: Test) => void): void

/** Assertions */

/** Assert that value is truthy */
ok(value: any, msg?: string): void
true(value: any, msg?: string): void
assert(value: any, msg?: string): void

/** Assert that value is falsy */
notOk(value: any, msg?: string): void
false(value: any, msg?: string): void
notok(value: any, msg?: string): void

/** Assert strict equality */
equal(actual: any, expected: any, msg?: string): void
equals(actual: any, expected: any, msg?: string): void
isEqual(actual: any, expected: any, msg?: string): void
is(actual: any, expected: any, msg?: string): void
strictEqual(actual: any, expected: any, msg?: string): void
strictEquals(actual: any, expected: any, msg?: string): void

/** Assert strict inequality */
notEqual(actual: any, expected: any, msg?: string): void
notEquals(actual: any, expected: any, msg?: string): void
notStrictEqual(actual: any, expected: any, msg?: string): void
notStrictEquals(actual: any, expected: any, msg?: string): void
doesNotEqual(actual: any, expected: any, msg?: string): void
isNotEqual(actual: any, expected: any, msg?: string): void
isNot(actual: any, expected: any, msg?: string): void
not(actual: any, expected: any, msg?: string): void
isInequal(actual: any, expected: any, msg?: string): void

/** Assert loose equality */
looseEqual(actual: any, expected: any, msg?: string): void
looseEquals(actual: any, expected: any, msg?: string): void

/** Assert loose inequality */
notLooseEqual(actual: any, expected: any, msg?: string): void
notLooseEquals(actual: any, expected: any, msg?: string): void

/** Assert deep strict equality */
deepEqual(actual: any, expected: any, msg?: string): void
deepEquals(actual: any, expected: any, msg?: string): void
isEquivalent(actual: any, expected: any, msg?: string): void
same(actual: any, expected: any, msg?: string): void

/** Assert deep strict inequality */
notDeepEqual(actual: any, expected: any, msg?: string): void
notDeepEquals(actual: any, expected: any, msg?: string): void
notEquivalent(actual: any, expected: any, msg?: string): void
notDeeply(actual: any, expected: any, msg?: string): void
notSame(actual: any, expected: any, msg?: string): void
isNotDeepEqual(actual: any, expected: any, msg?: string): void
isNotDeeply(actual: any, expected: any, msg?: string): void
isNotEquivalent(actual: any, expected: any, msg?: string): void
isInequivalent(actual: any, expected: any, msg?: string): void

/** Assert deep loose equality */
deepLooseEqual(actual: any, expected: any, msg?: string): void

/** Assert deep loose inequality */
notDeepLooseEqual(actual: any, expected: any, msg?: string): void

/** Assert function throws */
throws(fn: () => any, expected?: RegExp | Function, msg?: string): void

/** Assert function does not throw */
doesNotThrow(fn: () => any, expected?: RegExp | Function, msg?: string): void

/** Assert promise rejects */
rejects(promise: Promise<any>, expected?: RegExp | Function, msg?: string): Promise<void>

/** Assert promise resolves */
resolves(promise: Promise<any>, msg?: string): Promise<void>
doesNotReject(promise: Promise<any>, expected?: RegExp | Function, msg?: string): Promise<void>

/** Force a passing assertion */
pass(msg?: string): void

/** Force a failing assertion */
fail(msg?: string): void

/** Assert no error */
error(err: any, msg?: string): void
ifError(err: any, msg?: string): void
ifErr(err: any, msg?: string): void
iferror(err: any, msg?: string): void

/** Custom assertion function */
assertion(fn: Function, ...args: any[]): void
}

/**
* Test options
*/
export interface TestOptions {
skip?: boolean
todo?: boolean
timeout?: number
concurrency?: number
}

/**
* Test function
*/
export interface TestFunction {
/** Run a test */
(name: string, cb: (t: Test) => void): void
(name: string, opts: TestOptions, cb: (t: Test) => void): void
(cb: (t: Test) => void): void
(opts: TestOptions, cb: (t: Test) => void): void

/** Only run this test */
only: TestFunction

/** Skip this test */
skip: TestFunction

/** Register a callback to run after all tests finish */
onFinish(fn: () => void): void
}

declare const test: TestFunction

export { test }

export default test
Loading