Skip to content
Open
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
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ test('tspl', (t) => {

Create a plan for the current test.

Here are the options:
The options are:

* `plan`: how many assertions are planned
* `plan`: The number of assertions that are expected to run.
* `wait`: If `true`, the plan waits indefinitely for all assertions and subtests to run.
If `false`, the plan performs an immediate check after the test function completes, without waiting for any pending assertions or subtests. Any assertions or subtests that complete after this check will not be counted towards the plan.
If a `number`, it specifies the maximum wait time in milliseconds before timing out while waiting for expected assertions and subtests to be matched. If the timeout is reached, the test will fail. **Default**: `false`.
* `done`: an optional callback that will be called when the plan is completed.

### `Plan`

Expand All @@ -63,6 +67,24 @@ as well as:
* `end()`: a function to complete the plan
* `completed`: a promise that will resolve when the plan is completed.

### `t.plan(count, options?)`

You can also override the plan by calling `t.plan(count, options)`.

If you call `t.plan()` after creating a plan with `tspl()`, the options passed to `t.plan()` will override those passed to `tspl()`.


The options are:

* `count`: The number of assertions that are expected to run.
* `wait`: If `true`, the plan waits indefinitely for all assertions and subtests to run.
If `false`, the plan performs an immediate check after the test function completes, without waiting for any pending assertions or subtests. Any assertions or subtests that complete after this check will not be counted towards the plan.
If a `number`, it specifies the maximum wait time in milliseconds before timing out while waiting for expected assertions and subtests to be matched. If the timeout is reached, the test will fail. **Default**: `false`.

### `t.assert`

An object containing assertion methods bound to `context`. The top-level functions from the `node:assert` module are exposed here for the purpose of creating test plans.

## License

MIT
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"main": "tspl.js",
"types": "tspl.d.ts",
"scripts": {
"build:types": "npm run delete:types && tsc --project tsconfig.types.json",
"delete:types": "node -e \"import { rmSync } from 'fs'; try {rmSync('./tspl.d.ts'); } catch {} \"",
"lint": "standard",
"lint:fix": "standard --fix",
"test": "npm run test:unit && npm run test:types",
Expand All @@ -22,11 +24,10 @@
"url": "git+https://github.com/mcollina/tspl.git"
},
"license": "MIT",
"dependencies": {
},
"devDependencies": {
"@types/node": "^24.5.2",
"standard": "^17.1.0",
"tsd": "^0.33.0"
"tsd": "^0.33.0",
"typescript": "^5.9.2"
}
}
259 changes: 259 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,60 @@ test('simple test plan', async (t) => {
end()
})

test('simple test plan with passing with providing done to tspl', (t, done) => {
const { strictEqual, end } = tspl(t, { done })
strictEqual(1, 1)
end()
})

test('simple test plan failing and providing done to tspl', () => {
const { strictEqual, end } = tspl({
after: () => {},
strictEqual: assert.strictEqual
}, { plan: 2 })
strictEqual(1, 1)

assert.throws(() => end(), new assert.AssertionError({
message: 'The plan was not completed',
operator: 'strictEqual',
actual: 1,
expected: 2
}))
})

test('simple test plan failing with not providing done to tspl', () => {
const { strictEqual, end } = tspl({
after: () => {},
strictEqual: assert.strictEqual
}, { plan: 2 })
strictEqual(1, 1)

assert.throws(() => end(), new assert.AssertionError({
message: 'The plan was not completed',
operator: 'strictEqual',
actual: 1,
expected: 2
}))
})

test('simple test plan failing with providing done to tspl', () => {
const testDone = (err) => {
assert.deepStrictEqual(err, new assert.AssertionError({
message: 'The plan was not completed',
operator: 'strictEqual',
actual: 1,
expected: 2
}))
}
const { strictEqual, end } = tspl({
after: () => {},
strictEqual: assert.strictEqual
}, { plan: 2, done: testDone })
strictEqual(1, 1)

end()
})

test('simple test plan failing', async (t) => {
let _fn
tspl({
Expand Down Expand Up @@ -93,3 +147,208 @@ test('await plan', async (t) => {

await plan
})

test('t.assert is the same as assert', async (t) => {
t = tspl(t, { plan: 1 })
t.assert.strictEqual(1, 1)
})

test('use t.assert and await plan', async (t) => {
const plan = tspl(t, { plan: 1 })

setImmediate(() => {
plan.assert.strictEqual(1, 1)
})

await plan
})

test('it is possible to override the configure plan by calling .plan()', async (t) => {
t = tspl(t, { plan: 2 })

t.plan(1)

t.assert.strictEqual(1, 1)

await t.completed
})

test('it is possible to configure wait on initialization - true', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
}, { plan: 1, wait: true })

setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 100)

await _fn()
})

test('it is possible to configure wait on initialization - false and pass', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
}, { plan: 1, wait: false })

plan.assert.strictEqual(1, 1)

await _fn()
})

test('it is possible to configure wait on initialization - false and fail', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
}, { plan: 2, wait: false })

plan.strictEqual(1, 1)
setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 20)

await assert.rejects(_fn, new assert.AssertionError({
message: 'The plan was not completed',
operator: 'strictEqual',
actual: 1,
expected: 2
}))
})

test('it is possible to configure wait on initialization - number and pass', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
}, { plan: 2, wait: 20 })

plan.strictEqual(1, 1)
setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 10)

await _fn()
})

test('it is possible to configure wait on initialization - number and fail', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
}, { plan: 2, wait: 10 })

plan.strictEqual(1, 1)
setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 20)

await assert.rejects(_fn, new assert.AssertionError({
message: 'The plan was not completed',
operator: 'strictEqual',
actual: 1,
expected: 2
}))
})

test('it is possible to configure wait via t.plan - true', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
})

plan.plan(1, { wait: true })
setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 100)

await _fn()
})

test('it is possible to configure wait via t.plan - false and pass', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
})

plan.plan(1, { wait: false })
plan.assert.strictEqual(1, 1)

await _fn()
})

test('it is possible to configure wait via t.plan - false and fail', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
})

plan.plan(2, { wait: false })

plan.strictEqual(1, 1)
setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 20)

await assert.rejects(_fn, new assert.AssertionError({
message: 'The plan was not completed',
operator: 'strictEqual',
actual: 1,
expected: 2
}))
})

test('it is possible to configure wait via t.plan - number and pass', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
})

plan.plan(2, { wait: 20 })

plan.strictEqual(1, 1)
setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 10)

await _fn()
})

test('it is possible to configure wait via t.plan - number and fail', async (t) => {
let _fn
const plan = tspl({
after (fn) {
_fn = fn
}
})

plan.plan(2, { wait: 10 })

plan.strictEqual(1, 1)
setTimeout(() => {
plan.assert.strictEqual(1, 1)
}, 20)

await assert.rejects(_fn, new assert.AssertionError({
message: 'The plan was not completed',
operator: 'strictEqual',
actual: 1,
expected: 2
}))
})
23 changes: 23 additions & 0 deletions tsconfig.types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "esnext",
"lib": [
"esnext"
],
"types": [
"node"
],
"declaration": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"allowJs": true,
"emitDeclarationOnly": true,
"skipLibCheck": true,
"checkJs": true,
"removeComments": true
},
"include": [
"tspl.js"
]
}
35 changes: 18 additions & 17 deletions tspl.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import test from 'node:test'
import assert from 'node:assert'

type TestFn = Exclude<Parameters<typeof test.test>[0], undefined>
type TextContext = Exclude<Parameters<TestFn>[0], undefined>;

export interface Options {
plan?: number;
export = tspl;
declare function tspl(t: TestContext, opts?: Options): TSPLTestContext;
declare namespace tspl {
export { tspl as default, tspl, TSPLContext, Options, TestContext, AssertFns, TSPLTestContext, Plan };
}

export type Plan = Omit<typeof assert, 'CallTracker' | 'AssertionError' | 'strict'> & {
completed: Promise<void>
end: () => void
}

export declare function tspl (context: TextContext, opts?: Options): Plan;

export default tspl;
type TSPLContext = {
end: (err?: Error) => void;
completed: Promise<void>;
};
type Options = {
plan?: number;
wait?: boolean | number;
done?: (err?: Error) => void;
};
type TestContext = import("node:test").TestContext;
type AssertFns = Omit<typeof assert, "AssertionError" | "CallTracker" | "strict">;
type TSPLTestContext = TestContext & AssertFns & TSPLContext & PromiseLike<void>;
type Plan = TSPLTestContext;
import assert = require("assert");
Loading