1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 |
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x90
x90
x90
x90
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
|
I
|
/**
* QUnitX — universal test library that runs the same test file in Node.js, Deno, and browser.
*
* Wraps QUnit's assertion API over each runtime's native BDD test runner so you only
* write your tests once.
*
* @example
* ```js
* // math_test.js
* import { module, test } from "qunitx";
*
* module("Math", (hooks) => {
* hooks.beforeEach(function () {
* this.numbers = [1, 2, 3];
* });
*
* test("addition", function (assert) {
* assert.equal(this.numbers.reduce((sum, n) => sum + n, 0), 6);
* });
*
* test("async", async (assert) => {
* const n = await Promise.resolve(42);
* assert.strictEqual(n, 42);
* });
* });
* ```
*
* Both runtimes install it under the same name, so that one file runs on either.
* In a Deno project:
*
* ```sh
* deno add qunitx
* ```
*
* In a Node project:
*
* ```sh
* npm install --save-dev qunitx
* ```
*
* Then run it with whichever runner you have:
*
* ```sh
* deno test math_test.js
* node --test math_test.js
* ```
*
* @example
* ```js
* // The same test written with the BDD aliases — describe and it are the same
* // function objects as module and test, so everything above applies unchanged.
* import { describe, it } from "qunitx";
*
* describe("Math", (hooks) => {
* hooks.beforeEach(function () {
* this.numbers = [1, 2, 3];
* });
*
* it("addition", function (assert) {
* assert.equal(this.numbers.reduce((sum, n) => sum + n, 0), 6);
* });
*
* it("async", async (assert) => {
* const n = await Promise.resolve(42);
* assert.strictEqual(n, 42);
* });
* });
* ```
*
* `describe.skip`, `it.skip`, `it.todo`, runtime options, and nesting all behave
* identically to their `module` / `test` counterparts, and the commands above are
* unchanged.
*
* @module
*/
import '../../vendor/qunit.js';
import { AssertionError as DenoAssertionError } from 'jsr:@std/assert@^1.0.17';
import Assert from '../shared/assert.ts';
import { filterStack } from '../shared/filter-stack.ts';
import ModuleContext from '../shared/module-context.ts';
import TestContext from '../shared/test-context.ts';
import type { AssertionErrorOptions } from '../types.ts';
import Module from './module.ts';
import Test from './test.ts';
/**
* Thrown when an assertion fails. Extends Deno's built-in `AssertionError`
* so it integrates cleanly with Deno's test runner output.
*
* You rarely construct this directly — assertion methods on {@linkcode Assert}
* throw it automatically on failure.
*
* @example
* ```js
* import { AssertionError } from "qunitx";
*
* try {
* throw new AssertionError({ message: "something went wrong" });
* } catch (e) {
* console.log(e instanceof AssertionError); // true
* }
* ```
*/
export class AssertionError extends DenoAssertionError {
/**
* Builds an assertion failure, trimming the stack to the caller's frame.
*
* @param {object} object - Failure details.
* @param {string} [object.message] - Failure message; defaults to `"Assertion failed"`.
* @param {Function} [object.stackStartFn] - Frames at and above this function are cut
* from the stack, so the trace starts at the caller's assertion rather than inside
* QUnitX.
*/
constructor(object: AssertionErrorOptions) {
super(object.message ?? 'Assertion failed');
if (object.stackStartFn) Error.captureStackTrace(this, object.stackStartFn);
this.stack = filterStack(this.stack);
}
}
Assert.QUnit = (globalThis as typeof globalThis & { QUnit: typeof Assert.QUnit }).QUnit;
Assert.AssertionError = AssertionError;
Assert.inspect = Deno.inspect;
ModuleContext.Assert = Assert;
TestContext.Assert = Assert;
Object.freeze(Assert);
Object.freeze(ModuleContext);
Object.freeze(TestContext);
export { Assert };
/**
* Defines a test module (suite). Wraps Deno's `describe()` and sets up the
* QUnit lifecycle — `before`, `beforeEach`, `afterEach`, and `after` hooks,
* assertion counting, and step tracking.
*
* Each {@linkcode test} inside the callback receives an {@linkcode Assert} instance.
* Modules can be nested by calling `module()` inside another module's callback.
*
* @param {string} moduleName - Name of the test suite.
* @param {object} [runtimeOptions] - Optional Deno BDD options forwarded to `describe()`
* (e.g. `{ concurrency: false }`, `{ permissions: { read: true } }`).
* @param {function} moduleContent - Callback that defines tests and hooks.
* Receives `(hooks, { moduleName, options })` where `hooks` exposes
* `before`, `beforeEach`, `afterEach`, and `after`.
* @example
* ```js
* import { module, test } from "qunitx";
*
* module("Math", (hooks) => {
* hooks.before((assert) => {
* assert.step("before hook ran");
* });
*
* test("addition", (assert) => {
* assert.equal(2 + 2, 4);
* });
* });
* ```
* @example
* ```js
* // Nested modules
* module("Outer", () => {
* module("Inner", () => {
* test("nested test", (assert) => {
* assert.ok(true);
* });
* });
* });
* ```
*/
export { default as module } from './module.ts';
/**
* BDD-style alias for {@linkcode module}. The same function object, so
* `describe.skip` and `describe.todo` behave exactly like `module.skip` and
* `module.todo`.
*
* @example
* ```js
* import { describe, it } from "qunitx";
*
* describe("Math", (hooks) => {
* hooks.beforeEach((assert) => {
* assert.step("setup");
* });
*
* it("adds", (assert) => {
* assert.equal(1 + 1, 2);
* });
* });
* ```
*/
export { default as describe } from './module.ts';
/**
* BDD-style alias for {@linkcode test}. The same function object, so `it.skip`
* and `it.todo` behave exactly like `test.skip` and `test.todo`.
*
* @example
* ```js
* import { describe, it } from "qunitx";
*
* describe("Math", () => {
* it("adds", (assert) => {
* assert.equal(1 + 1, 2);
* });
*
* it.skip("divides", (assert) => {
* assert.equal(4 / 2, 2);
* });
* });
* ```
*/
export { default as it } from './test.ts';
/**
* Registers a skipped test. Equivalent to `test.skip`. The test body is never
* executed and the test is reported as ignored by Deno's runner.
*
* @param {string} testName - Name of the test to skip.
* @param {function} [_testContent] - Optional body (ignored — the test will not run).
* @example
* ```js
* import { module, skip } from "qunitx";
*
* module("Math", () => {
* skip("addition is not yet implemented", (assert) => {
* assert.equal(1 + 1, 2);
* });
* });
* ```
*/
export const skip: (testName: string, _testContent?: unknown) => void = Test.skip;
/**
* Defines an individual test. Wraps Deno's `it()` and handles the full QUnit
* lifecycle: `beforeEach`/`afterEach` hooks, async assertion waiting, and step
* verification. Must be called inside a {@linkcode module} callback.
*
* The test callback receives `(assert, { testName, options })` where `assert`
* is an {@linkcode Assert} instance.
*
* @param {string} testName - Name of the test.
* @param {object} [runtimeOptions] - Optional options forwarded to `it()`.
* Use `{ skip: true | string }` to skip the test, `{ todo: true | string }` to mark it as todo
* (both map to Deno's `{ ignore: true }`). Other Deno BDD options like
* `{ concurrency: false }` or `{ sanitizeExit: false }` are forwarded as-is.
* @param {function} testContent - Test callback receiving `(assert, { testName, options })`.
* @example
* ```js
* import { module, test } from "qunitx";
*
* module("Math", () => {
* test("addition", (assert) => {
* assert.equal(1 + 1, 2);
* });
*
* test("async resolves correctly", async (assert) => {
* const result = await Promise.resolve(42);
* assert.strictEqual(result, 42);
* });
* });
* ```
*/
export { default as test } from './test.ts';
/**
* Registers a todo test. Equivalent to `test.todo`. The test body is never executed
* and the test is reported as ignored by Deno's runner, which has no native todo concept.
*
* @param {string} testName - Name of the test to mark as todo.
* @param {function} [_testContent] - Optional body (ignored — the test will not run).
* @example
* ```js
* import { module, todo } from "qunitx";
*
* module("Math", () => {
* todo("addition is not yet implemented", (assert) => {
* assert.equal(1 + 1, 2);
* });
* });
* ```
*/
export const todo: (testName: string, _testContent?: unknown) => void = Test.todo;
/**
* The default export provides the full QUnitX API as a single object.
*
* @example
* ```js
* import qunitx from "qunitx";
*
* qunitx.module("Math", () => {
* qunitx.test("addition", (assert) => {
* assert.equal(1 + 1, 2);
* });
* });
* ```
*
* @property {Function} module - Defines a test suite. Wraps Deno's `describe()` with
* QUnit lifecycle hooks (`before`, `beforeEach`, `afterEach`, `after`).
* See the named {@linkcode module} export for full parameter documentation.
* @property {Function} test - Defines an individual test inside a `module()` callback.
* Receives an {@linkcode Assert} instance as its first argument.
* See the named {@linkcode test} export for full parameter documentation.
* @property {Function} describe - BDD-style alias for `module`. Same function object.
* @property {Function} it - BDD-style alias for `test`. Same function object.
* @property {typeof AssertionError} AssertionError - The error class thrown when an
* assertion fails. Extends Deno's built-in `AssertionError`.
* @property {object} config - Runtime configuration object (currently unused; reserved
* for future QUnit config compatibility).
*/
export default {
AssertionError: Assert.AssertionError,
describe: Module,
it: Test,
module: Module,
test: Test,
config: {},
};
/** Public types referenced by the signatures above, so consumers can name them. */
export type { HookFn, HooksObject, PushResultInfo, TestFn } from '../types.ts';
|