qunitx 1.2.4 → 1.2.6

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.
@@ -1,6 +1,6 @@
1
1
  import { describe, beforeAll, afterAll } from "jsr:@std/testing/bdd";
2
2
  import type Assert from '../shared/assert.ts';
3
- import type { HooksObject } from '../types.ts';
3
+ import type { HookFn, HooksObject } from '../types.ts';
4
4
  import ModuleContext from '../shared/module-context.ts';
5
5
  export type { Assert };
6
6
  export type { HookFn, HooksObject, PushResultInfo } from '../types.ts';
@@ -32,13 +32,13 @@ export type { HookFn, HooksObject, PushResultInfo } from '../types.ts';
32
32
  * });
33
33
  * ```
34
34
  */
35
- export default function module(moduleName: string, moduleContent: (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void): void;
35
+ export default function module(moduleName: string, moduleContent: (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown; context: Record<string, unknown> }) => void): void;
36
36
  /** Defines a test module (suite) with optional Deno BDD runtime options forwarded to `describe()`. */
37
- export default function module(moduleName: string, runtimeOptions: object, moduleContent: (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void): void;
37
+ export default function module(moduleName: string, runtimeOptions: object, moduleContent: (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown; context: Record<string, unknown> }) => void): void;
38
38
  export default function module(
39
39
  moduleName: string,
40
40
  runtimeOptions: object | ((hooks: HooksObject<Assert>) => void),
41
- moduleContent?: (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void,
41
+ moduleContent?: (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown; context: Record<string, unknown> }) => void,
42
42
  ): void {
43
43
  const targetRuntimeOptions = moduleContent ? runtimeOptions as object : {};
44
44
  const { skip } = targetRuntimeOptions as { skip?: boolean | string };
@@ -57,8 +57,8 @@ export default function module(
57
57
  const moduleContext = new ModuleContext(moduleName);
58
58
 
59
59
  describe(moduleName, { ...targetRuntimeOptions }, function () {
60
- const beforeHooks: ((assert: Assert) => void | Promise<void>)[] = [];
61
- const afterHooks: ((assert: Assert) => void | Promise<void>)[] = [];
60
+ const beforeHooks: HookFn<Assert>[] = [];
61
+ const afterHooks: HookFn<Assert>[] = [];
62
62
 
63
63
  beforeAll(async function () {
64
64
  // before() assertions are attributed to the first direct test only (matching QUnit's model).
@@ -67,7 +67,7 @@ export default function module(
67
67
  const beforeAssert = firstTest ? firstTest.assert! : moduleContext.assert!;
68
68
 
69
69
  for (const hook of beforeHooks) {
70
- await hook.call(moduleContext.userContext, beforeAssert);
70
+ await hook.call(moduleContext.userContext, beforeAssert, { context: moduleContext.userContext });
71
71
  }
72
72
  });
73
73
 
@@ -79,7 +79,7 @@ export default function module(
79
79
  const lastTest = moduleContext.tests[moduleContext.tests.length - 1];
80
80
  if (lastTest) {
81
81
  for (let j = afterHooks.length - 1; j >= 0; j--) {
82
- await afterHooks[j]!.call(lastTest.userContext, lastTest.assert!);
82
+ await afterHooks[j]!.call(lastTest.userContext, lastTest.assert!, { context: lastTest.userContext });
83
83
  }
84
84
  }
85
85
 
@@ -101,7 +101,7 @@ export default function module(
101
101
  after(afterFn) {
102
102
  afterHooks.push(afterFn);
103
103
  }
104
- }, { moduleName, options: runtimeOptions });
104
+ }, { moduleName, options: runtimeOptions, context: moduleContext.userContext });
105
105
 
106
106
  ModuleContext.currentModuleChain.pop();
107
107
  });
@@ -127,3 +127,25 @@ export default function module(
127
127
  module.skip = function skipModule(moduleName: string, _moduleContent?: unknown): void {
128
128
  describe(moduleName, { ignore: true }, function () {});
129
129
  };
130
+
131
+ /**
132
+ * Marks all tests inside a module as todo. Equivalent to `QUnit.module.todo`.
133
+ * The module is registered as ignored by Deno's runner; no test bodies run.
134
+ * Deno has no native "todo module" concept, so this maps to ignore.
135
+ *
136
+ * @param {string} moduleName - Name of the module to mark as todo.
137
+ * @param {function} [_moduleContent] - Optional body (ignored — no tests run).
138
+ * @example
139
+ * ```js ignore
140
+ * import { module, test } from "qunitx";
141
+ *
142
+ * module.todo("Math — not yet implemented", () => {
143
+ * test("addition", (assert) => {
144
+ * assert.equal(1 + 1, 2);
145
+ * });
146
+ * });
147
+ * ```
148
+ */
149
+ module.todo = function todoModule(moduleName: string, _moduleContent?: unknown): void {
150
+ describe(moduleName, { ignore: true }, function () {});
151
+ };
@@ -36,13 +36,13 @@ export type { PushResultInfo } from '../types.ts';
36
36
  * });
37
37
  * ```
38
38
  */
39
- export default function test(testName: string, testContent: (assert: Assert, meta: { testName: string; options: unknown }) => void | Promise<void>): void;
39
+ export default function test(testName: string, testContent: (assert: Assert, meta: { testName: string; options: unknown; context: Record<string, unknown> }) => void | Promise<void>): void;
40
40
  /** Defines an individual test with optional Deno BDD runtime options forwarded to `it()`. */
41
- export default function test(testName: string, runtimeOptions: object, testContent: (assert: Assert, meta: { testName: string; options: unknown }) => void | Promise<void>): void;
41
+ export default function test(testName: string, runtimeOptions: object, testContent: (assert: Assert, meta: { testName: string; options: unknown; context: Record<string, unknown> }) => void | Promise<void>): void;
42
42
  export default function test(
43
43
  testName: string,
44
- runtimeOptions: object | ((assert: Assert, meta: { testName: string; options: unknown }) => void | Promise<void>),
45
- testContent?: (assert: Assert, meta: { testName: string; options: unknown }) => void | Promise<void>,
44
+ runtimeOptions: object | ((assert: Assert, meta: { testName: string; options: unknown; context: Record<string, unknown> }) => void | Promise<void>),
45
+ testContent?: (assert: Assert, meta: { testName: string; options: unknown; context: Record<string, unknown> }) => void | Promise<void>,
46
46
  ): void {
47
47
  const moduleContext = ModuleContext.lastModule;
48
48
  if (!moduleContext) {
@@ -68,25 +68,39 @@ export default function test(
68
68
  const userContext = Object.create(moduleContext.userContext);
69
69
  context.userContext = userContext;
70
70
 
71
- it(testName, { ...targetRuntimeOptions }, async function () {
72
- for (const module of context.module!.moduleChain) {
73
- for (const hook of module.beforeEachHooks) {
74
- await hook.call(userContext, context.assert!);
75
- }
76
- }
71
+ it(testName, { ...targetRuntimeOptions }, function () {
72
+ return new Promise((resolve, reject) => {
73
+ context.rejectTimeout = reject;
77
74
 
78
- const result = await targetTestContent.call(userContext, context.assert!, { testName, options: runtimeOptions });
75
+ (async () => {
76
+ const hookMeta = { context: userContext };
79
77
 
80
- await context.assert!.waitForAsyncOps();
78
+ try {
79
+ for (const module of context.module!.moduleChain) {
80
+ for (const hook of module.beforeEachHooks) {
81
+ await hook.call(userContext, context.assert!, hookMeta);
82
+ }
83
+ }
81
84
 
82
- for (let i = context.module!.moduleChain.length - 1; i >= 0; i--) {
83
- const module = context.module!.moduleChain[i];
84
- for (let j = module.afterEachHooks.length - 1; j >= 0; j--) {
85
- await module.afterEachHooks[j]!.call(userContext, context.assert!);
86
- }
87
- }
85
+ const result = await targetTestContent.call(userContext, context.assert!, { testName, options: runtimeOptions, context: userContext });
88
86
 
89
- return result;
87
+ await context.assert!.waitForAsyncOps();
88
+
89
+ for (let i = context.module!.moduleChain.length - 1; i >= 0; i--) {
90
+ const module = context.module!.moduleChain[i];
91
+ for (let j = module.afterEachHooks.length - 1; j >= 0; j--) {
92
+ await module.afterEachHooks[j]!.call(userContext, context.assert!, hookMeta);
93
+ }
94
+ }
95
+
96
+ resolve(result);
97
+ } catch (err) {
98
+ reject(err);
99
+ } finally {
100
+ context.clearTimeoutHandle();
101
+ }
102
+ })();
103
+ });
90
104
  });
91
105
  }
92
106
 
@@ -61,6 +61,7 @@ export default class Assert {
61
61
  }
62
62
 
63
63
  this.test.timeout = number;
64
+ this.test.setTimeoutDuration(number);
64
65
  }
65
66
 
66
67
  /**
@@ -12,7 +12,7 @@ export default class ModuleContext {
12
12
 
13
13
  name!: string;
14
14
  assert!: Assert;
15
- userContext!: object;
15
+ userContext!: Record<string, unknown>;
16
16
 
17
17
  // Internal fallback assert for modules with no direct tests
18
18
  context = new TestContext();
@@ -44,6 +44,29 @@ export default class TestContext {
44
44
  this.#timeout = value;
45
45
  }
46
46
 
47
+ rejectTimeout: ((err: Error) => void) | undefined;
48
+ #timeoutHandle: ReturnType<typeof setTimeout> | undefined;
49
+ #timedOut = false;
50
+
51
+ setTimeoutDuration(ms: number) {
52
+ if (this.#timeoutHandle !== undefined) {
53
+ clearTimeout(this.#timeoutHandle);
54
+ this.#timeoutHandle = undefined;
55
+ }
56
+ if (!this.rejectTimeout) return;
57
+ this.#timeoutHandle = setTimeout(() => {
58
+ this.#timedOut = true;
59
+ this.rejectTimeout!(new Error(`Test timed out after ${ms}ms`));
60
+ }, ms);
61
+ }
62
+
63
+ clearTimeoutHandle() {
64
+ if (this.#timeoutHandle !== undefined) {
65
+ clearTimeout(this.#timeoutHandle);
66
+ this.#timeoutHandle = undefined;
67
+ }
68
+ }
69
+
47
70
  #steps: string[] = [];
48
71
  get steps() {
49
72
  return this.#steps;
@@ -68,7 +91,7 @@ export default class TestContext {
68
91
  this.#totalExecutedAssertions = value;
69
92
  }
70
93
 
71
- userContext: object = {};
94
+ userContext: Record<string, unknown> = {};
72
95
 
73
96
  constructor(name?: string, moduleContext?: ModuleContext) {
74
97
  if (moduleContext) {
@@ -80,6 +103,7 @@ export default class TestContext {
80
103
  }
81
104
 
82
105
  finish() {
106
+ if (this.#timedOut) return;
83
107
  if (this.totalExecutedAssertions === 0) {
84
108
  this.assert!.pushResult({
85
109
  result: false,
package/shims/types.ts CHANGED
@@ -37,8 +37,9 @@ export interface TestState {
37
37
  asyncOps: Promise<void>[];
38
38
  timeout?: number;
39
39
  expectedAssertionCount?: number;
40
- userContext?: object;
40
+ userContext?: Record<string, unknown>;
41
41
  module?: { name: string };
42
+ setTimeoutDuration(ms: number): void;
42
43
  }
43
44
 
44
45
  /** Minimal module shape that Assert needs to resolve a fallback test context. */
@@ -46,11 +47,11 @@ export interface ModuleState {
46
47
  context: TestState;
47
48
  }
48
49
 
49
- /** A lifecycle hook callback that receives an {@linkcode Assert} instance. */
50
- export type HookFn<A = unknown> = (assert: A) => void | Promise<void>;
50
+ /** A lifecycle hook callback that receives an {@linkcode Assert} instance and a meta object with the shared context. */
51
+ export type HookFn<A = unknown> = (assert: A, meta: { context: Record<string, unknown> }) => void | Promise<void>;
51
52
  export type TestFn<A = unknown> = (
52
53
  assert: A,
53
- meta: { testName: string; options: unknown },
54
+ meta: { testName: string; options: unknown; context: Record<string, unknown> },
54
55
  ) => void | Promise<unknown>;
55
56
 
56
57
  /** Lifecycle hooks available inside a `module()` callback. */