qunitx 1.2.6 → 1.2.8

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.
@@ -27,11 +27,8 @@ function objectType(obj) {
27
27
  return typeof obj;
28
28
  }
29
29
  }
30
- function is(type, obj) {
31
- return objectType(obj) === type;
32
- }
33
30
  function objectValues(obj, allowArray = true) {
34
- const vals = allowArray && is("array", obj) ? [] : {};
31
+ const vals = allowArray && objectType(obj) === "array" ? [] : {};
35
32
  for (const key in obj) {
36
33
  if (hasOwn.call(obj, key)) {
37
34
  const val = obj[key];
@@ -63,13 +60,15 @@ function validateExpectedExceptionArgs(expected, message, assertionMethod) {
63
60
  expected = void 0;
64
61
  return [expected, message];
65
62
  } else {
66
- throw new Error("assert." + assertionMethod + " does not accept a string value for the expected argument.\nUse a non-string object value (e.g. RegExp or validator function) instead if necessary.");
63
+ throw new Error(
64
+ `assert.${assertionMethod} does not accept a string value for the expected argument.
65
+ Use a non-string object value (e.g. RegExp or validator function) instead if necessary.`
66
+ );
67
67
  }
68
68
  }
69
- const valid = !expected || // TODO: be more explicit here
70
- expectedType === "regexp" || expectedType === "function" || expectedType === "object";
69
+ const valid = !expected || expectedType === "regexp" || expectedType === "function" || expectedType === "object";
71
70
  if (!valid) {
72
- throw new Error("Invalid expected value type (" + expectedType + ") provided to assert." + assertionMethod + ".");
71
+ throw new Error(`Invalid expected value type (${expectedType}) provided to assert.${assertionMethod}.`);
73
72
  }
74
73
  return [expected, message];
75
74
  }
@@ -99,14 +98,13 @@ function validateException(actual, expected, message) {
99
98
  function errorString(error) {
100
99
  const resultErrorString = String(error);
101
100
  if (resultErrorString.slice(0, 7) === "[object") {
102
- return (error.name || "Error") + (error.message ? ": ".concat(error.message) : "");
103
- } else {
104
- return resultErrorString;
101
+ const name = error.name || "Error";
102
+ const msg = error.message;
103
+ return msg ? `${name}: ${msg}` : name;
105
104
  }
105
+ return resultErrorString;
106
106
  }
107
- var shared_default = { objectValues, objectValuesSubset, validateExpectedExceptionArgs, validateException };
108
107
  export {
109
- shared_default as default,
110
108
  objectType,
111
109
  objectValues,
112
110
  objectValuesSubset,
@@ -1,14 +1,14 @@
1
- import type Assert from './assert.d.ts';
2
1
  import type { HookFn } from '../types.d.ts';
2
+ import type Assert from './assert.d.ts';
3
3
  import TestContext from './test-context.d.ts';
4
4
  export default class ModuleContext {
5
5
  static Assert: typeof Assert;
6
6
  static currentModuleChain: ModuleContext[];
7
- static get lastModule(): ModuleContext;
7
+ static get lastModule(): ModuleContext | undefined;
8
8
  name: string;
9
9
  assert: Assert;
10
10
  userContext: Record<string, unknown>;
11
- context: TestContext;
11
+ testContext: TestContext;
12
12
  moduleChain: ModuleContext[];
13
13
  beforeEachHooks: HookFn<Assert>[];
14
14
  afterEachHooks: HookFn<Assert>[];
@@ -3,21 +3,21 @@ class ModuleContext {
3
3
  static Assert;
4
4
  static currentModuleChain = [];
5
5
  static get lastModule() {
6
- return this.currentModuleChain[this.currentModuleChain.length - 1];
6
+ return this.currentModuleChain.at(-1);
7
7
  }
8
8
  name;
9
9
  assert;
10
10
  userContext;
11
11
  // Internal fallback assert for modules with no direct tests
12
- context = new TestContext();
12
+ testContext = new TestContext();
13
13
  moduleChain = [];
14
14
  beforeEachHooks = [];
15
15
  afterEachHooks = [];
16
16
  tests = [];
17
17
  constructor(name) {
18
- const parentModule = ModuleContext.currentModuleChain[ModuleContext.currentModuleChain.length - 1];
18
+ const parentModule = ModuleContext.currentModuleChain.at(-1);
19
19
  ModuleContext.currentModuleChain.push(this);
20
- this.moduleChain = ModuleContext.currentModuleChain.slice(0);
20
+ this.moduleChain = [...ModuleContext.currentModuleChain];
21
21
  this.name = parentModule ? `${parentModule.name} > ${name}` : name;
22
22
  this.assert = new ModuleContext.Assert(this);
23
23
  this.userContext = parentModule ? Object.create(parentModule.userContext) : /* @__PURE__ */ Object.create(null);
@@ -3,26 +3,18 @@ import type ModuleContext from './module-context.d.ts';
3
3
  export default class TestContext {
4
4
  #private;
5
5
  static Assert: typeof Assert;
6
- get name(): string | undefined;
7
- set name(value: string | undefined);
8
- get module(): ModuleContext | undefined;
9
- set module(value: ModuleContext | undefined);
10
- get asyncOps(): Promise<void>[];
11
- set asyncOps(value: Promise<void>[]);
12
- get assert(): Assert | undefined;
13
- set assert(value: Assert | undefined);
14
- get timeout(): number | undefined;
15
- set timeout(value: number | undefined);
6
+ name: string | undefined;
7
+ module: ModuleContext | undefined;
8
+ asyncOps: Promise<void>[];
9
+ assert: Assert | undefined;
10
+ timeout: number | undefined;
11
+ steps: string[];
12
+ expectedAssertionCount: number | undefined;
13
+ totalExecutedAssertions: number;
14
+ userContext: Record<string, unknown>;
16
15
  rejectTimeout: ((err: Error) => void) | undefined;
16
+ constructor(name?: string, moduleContext?: ModuleContext);
17
17
  setTimeoutDuration(ms: number): void;
18
18
  clearTimeoutHandle(): void;
19
- get steps(): string[];
20
- set steps(value: string[]);
21
- get expectedAssertionCount(): number | undefined;
22
- set expectedAssertionCount(value: number | undefined);
23
- get totalExecutedAssertions(): number;
24
- set totalExecutedAssertions(value: number);
25
- userContext: Record<string, unknown>;
26
- constructor(name?: string, moduleContext?: ModuleContext);
27
19
  finish(): void;
28
20
  }
@@ -1,43 +1,25 @@
1
1
  class TestContext {
2
2
  static Assert;
3
- #name;
4
- get name() {
5
- return this.#name;
6
- }
7
- set name(value) {
8
- this.#name = value;
9
- }
10
- #module;
11
- get module() {
12
- return this.#module;
13
- }
14
- set module(value) {
15
- this.#module = value;
16
- }
17
- #asyncOps = [];
18
- get asyncOps() {
19
- return this.#asyncOps;
20
- }
21
- set asyncOps(value) {
22
- this.#asyncOps = value;
23
- }
24
- #assert;
25
- get assert() {
26
- return this.#assert;
27
- }
28
- set assert(value) {
29
- this.#assert = value;
30
- }
31
- #timeout;
32
- get timeout() {
33
- return this.#timeout;
34
- }
35
- set timeout(value) {
36
- this.#timeout = value;
37
- }
3
+ name;
4
+ module;
5
+ asyncOps = [];
6
+ assert;
7
+ timeout;
8
+ steps = [];
9
+ expectedAssertionCount;
10
+ totalExecutedAssertions = 0;
11
+ userContext = {};
38
12
  rejectTimeout;
39
13
  #timeoutHandle;
40
14
  #timedOut = false;
15
+ constructor(name, moduleContext) {
16
+ if (moduleContext) {
17
+ this.name = `${moduleContext.name} | ${name}`;
18
+ this.module = moduleContext;
19
+ this.module.tests.push(this);
20
+ this.assert = new TestContext.Assert(moduleContext, this);
21
+ }
22
+ }
41
23
  setTimeoutDuration(ms) {
42
24
  if (this.#timeoutHandle !== void 0) {
43
25
  clearTimeout(this.#timeoutHandle);
@@ -55,59 +37,29 @@ class TestContext {
55
37
  this.#timeoutHandle = void 0;
56
38
  }
57
39
  }
58
- #steps = [];
59
- get steps() {
60
- return this.#steps;
61
- }
62
- set steps(value) {
63
- this.#steps = value;
64
- }
65
- #expectedAssertionCount;
66
- get expectedAssertionCount() {
67
- return this.#expectedAssertionCount;
68
- }
69
- set expectedAssertionCount(value) {
70
- this.#expectedAssertionCount = value;
71
- }
72
- #totalExecutedAssertions = 0;
73
- get totalExecutedAssertions() {
74
- return this.#totalExecutedAssertions;
75
- }
76
- set totalExecutedAssertions(value) {
77
- this.#totalExecutedAssertions = value;
78
- }
79
- userContext = {};
80
- constructor(name, moduleContext) {
81
- if (moduleContext) {
82
- this.name = `${moduleContext.name} | ${name}`;
83
- this.module = moduleContext;
84
- this.module.tests.push(this);
85
- this.assert = new TestContext.Assert(moduleContext, this);
86
- }
87
- }
88
40
  finish() {
89
41
  if (this.#timedOut) return;
90
- if (this.totalExecutedAssertions === 0) {
91
- this.assert.pushResult({
92
- result: false,
93
- actual: this.totalExecutedAssertions,
94
- expected: "> 0",
95
- message: `Expected at least one assertion to be run for test: ${this.name}`
96
- });
97
- } else if (this.steps.length > 0) {
42
+ if (this.steps.length > 0) {
98
43
  this.assert.pushResult({
99
44
  result: false,
100
45
  actual: this.steps,
101
46
  expected: [],
102
47
  message: `Expected assert.verifySteps() to be called before end of test after using assert.step(). Unverified steps: ${this.steps.join(", ")}`
103
48
  });
104
- } else if (this.expectedAssertionCount && this.expectedAssertionCount !== this.totalExecutedAssertions) {
49
+ } else if (this.expectedAssertionCount !== void 0 && this.expectedAssertionCount !== this.totalExecutedAssertions) {
105
50
  this.assert.pushResult({
106
51
  result: false,
107
52
  actual: this.totalExecutedAssertions,
108
53
  expected: this.expectedAssertionCount,
109
54
  message: `Expected ${this.expectedAssertionCount} assertions, but ${this.totalExecutedAssertions} were run for test: ${this.name}`
110
55
  });
56
+ } else if (this.expectedAssertionCount === void 0 && this.totalExecutedAssertions === 0) {
57
+ this.assert.pushResult({
58
+ result: false,
59
+ actual: this.totalExecutedAssertions,
60
+ expected: "> 0",
61
+ message: `Expected at least one assertion to be run for test: ${this.name}`
62
+ });
111
63
  }
112
64
  }
113
65
  }
package/dist/types.d.ts CHANGED
@@ -44,7 +44,7 @@ export interface TestState {
44
44
  }
45
45
  /** Minimal module shape that Assert needs to resolve a fallback test context. */
46
46
  export interface ModuleState {
47
- context: TestState;
47
+ testContext: TestState;
48
48
  }
49
49
  /** A lifecycle hook callback that receives an {@linkcode Assert} instance and a meta object with the shared context. */
50
50
  export type HookFn<A = unknown> = (assert: A, meta: {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "qunitx",
3
3
  "type": "module",
4
- "version": "1.2.6",
5
- "description": "A universal test framework for testing any js file on node.js, browser or deno with QUnit API",
4
+ "version": "1.2.8",
5
+ "description": "Universal test library run the same test file in Node.js, Deno, and the browser with QUnit's battle-tested assertion API",
6
6
  "author": "Izel Nakri",
7
7
  "license": "MIT",
8
8
  "keywords": [
@@ -24,12 +24,12 @@
24
24
  *
25
25
  * @module
26
26
  */
27
- import { AssertionError as DenoAssertionError } from "jsr:@std/assert";
28
- import type { AssertionErrorOptions } from "../types.ts";
29
27
  import '../../vendor/qunit.js';
28
+ import { AssertionError as DenoAssertionError } from 'jsr:@std/assert';
30
29
  import Assert from '../shared/assert.ts';
31
30
  import ModuleContext from '../shared/module-context.ts';
32
31
  import TestContext from '../shared/test-context.ts';
32
+ import type { AssertionErrorOptions } from '../types.ts';
33
33
  import Module from './module.ts';
34
34
  import Test from './test.ts';
35
35
 
@@ -54,6 +54,7 @@ import Test from './test.ts';
54
54
  export class AssertionError extends DenoAssertionError {
55
55
  constructor(object: AssertionErrorOptions) {
56
56
  super(object.message ?? 'Assertion failed');
57
+ if (object.stackStartFn) Error.captureStackTrace(this, object.stackStartFn);
57
58
  }
58
59
  }
59
60
 
@@ -111,6 +112,25 @@ export { Assert };
111
112
  */
112
113
  export { default as module } from './module.ts';
113
114
 
115
+ /**
116
+ * Registers a skipped test. Equivalent to `test.skip`. The test body is never
117
+ * executed and the test is reported as ignored by Deno's runner.
118
+ *
119
+ * @param {string} testName - Name of the test to skip.
120
+ * @param {function} [_testContent] - Optional body (ignored — the test will not run).
121
+ * @example
122
+ * ```js
123
+ * import { module, skip } from "qunitx";
124
+ *
125
+ * module("Math", () => {
126
+ * skip("addition is not yet implemented", (assert) => {
127
+ * assert.equal(1 + 1, 2);
128
+ * });
129
+ * });
130
+ * ```
131
+ */
132
+ export const skip = Test.skip;
133
+
114
134
  /**
115
135
  * Defines an individual test. Wraps Deno's `it()` and handles the full QUnit
116
136
  * lifecycle: `beforeEach`/`afterEach` hooks, async assertion waiting, and step
@@ -142,25 +162,6 @@ export { default as module } from './module.ts';
142
162
  * ```
143
163
  */
144
164
  export { default as test } from './test.ts';
145
-
146
- /**
147
- * Registers a skipped test. Equivalent to `test.skip`. The test body is never
148
- * executed and the test is reported as ignored by Deno's runner.
149
- *
150
- * @param {string} testName - Name of the test to skip.
151
- * @param {function} [_testContent] - Optional body (ignored — the test will not run).
152
- * @example
153
- * ```js
154
- * import { module, skip } from "qunitx";
155
- *
156
- * module("Math", () => {
157
- * skip("addition is not yet implemented", (assert) => {
158
- * assert.equal(1 + 1, 2);
159
- * });
160
- * });
161
- * ```
162
- */
163
- export const skip = Test.skip;
164
165
  export const todo = Test.todo;
165
166
 
166
167
  /**
@@ -1,9 +1,7 @@
1
- import { describe, beforeAll, afterAll } from "jsr:@std/testing/bdd";
1
+ import { afterAll, beforeAll, describe } from 'jsr:@std/testing/bdd';
2
2
  import type Assert from '../shared/assert.ts';
3
- import type { HookFn, HooksObject } from '../types.ts';
4
3
  import ModuleContext from '../shared/module-context.ts';
5
- export type { Assert };
6
- export type { HookFn, HooksObject, PushResultInfo } from '../types.ts';
4
+ import type { HookFn, HooksObject } from '../types.ts';
7
5
 
8
6
  /**
9
7
  * Defines a test module (suite) for Deno's BDD test runner.
@@ -53,14 +51,14 @@ export default function module(
53
51
  return;
54
52
  }
55
53
 
56
- const targetModuleContent = (moduleContent ? moduleContent : runtimeOptions) as (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void;
54
+ const targetModuleContent = (moduleContent ?? runtimeOptions) as (hooks: HooksObject<Assert>, meta: { moduleName: string; options: unknown }) => void;
57
55
  const moduleContext = new ModuleContext(moduleName);
58
56
 
59
57
  describe(moduleName, { ...targetRuntimeOptions }, function () {
60
58
  const beforeHooks: HookFn<Assert>[] = [];
61
59
  const afterHooks: HookFn<Assert>[] = [];
62
60
 
63
- beforeAll(async function () {
61
+ beforeAll(async () => {
64
62
  // before() assertions are attributed to the first direct test only (matching QUnit's model).
65
63
  // Tests inherit parent context via prototype chain, so no Object.assign needed.
66
64
  const firstTest = moduleContext.tests[0];
@@ -72,19 +70,18 @@ export default function module(
72
70
  });
73
71
 
74
72
  afterAll(async () => {
75
- for (const testContext of moduleContext.tests) {
76
- await testContext.assert!.waitForAsyncOps();
77
- }
73
+ const allAsyncOps = moduleContext.tests.flatMap((t) => t.asyncOps);
74
+ if (allAsyncOps.length > 0) await Promise.all(allAsyncOps);
78
75
 
79
- const lastTest = moduleContext.tests[moduleContext.tests.length - 1];
76
+ const lastTest = moduleContext.tests.at(-1);
80
77
  if (lastTest) {
81
- for (let j = afterHooks.length - 1; j >= 0; j--) {
82
- await afterHooks[j]!.call(lastTest.userContext, lastTest.assert!, { context: lastTest.userContext });
78
+ for (const hook of afterHooks.toReversed()) {
79
+ await hook.call(lastTest.userContext, lastTest.assert!, { context: lastTest.userContext });
83
80
  }
84
81
  }
85
82
 
86
- for (let i = 0, len = moduleContext.tests.length; i < len; i++) {
87
- moduleContext.tests[i].finish();
83
+ for (const testCtx of moduleContext.tests) {
84
+ testCtx.finish();
88
85
  }
89
86
  });
90
87
 
@@ -101,7 +98,7 @@ export default function module(
101
98
  after(afterFn) {
102
99
  afterHooks.push(afterFn);
103
100
  }
104
- }, { moduleName, options: runtimeOptions, context: moduleContext.userContext });
101
+ }, { moduleName, options: targetRuntimeOptions, context: moduleContext.userContext });
105
102
 
106
103
  ModuleContext.currentModuleChain.pop();
107
104
  });
@@ -149,3 +146,6 @@ module.skip = function skipModule(moduleName: string, _moduleContent?: unknown):
149
146
  module.todo = function todoModule(moduleName: string, _moduleContent?: unknown): void {
150
147
  describe(moduleName, { ignore: true }, function () {});
151
148
  };
149
+
150
+ export type { Assert };
151
+ export type { HookFn, HooksObject, PushResultInfo } from '../types.ts';
@@ -1,9 +1,7 @@
1
- import { it } from "jsr:@std/testing/bdd";
1
+ import { it } from 'jsr:@std/testing/bdd';
2
2
  import type Assert from '../shared/assert.ts';
3
- import TestContext from '../shared/test-context.ts';
4
3
  import ModuleContext from '../shared/module-context.ts';
5
- export type { Assert };
6
- export type { PushResultInfo } from '../types.ts';
4
+ import TestContext from '../shared/test-context.ts';
7
5
 
8
6
  /**
9
7
  * Defines an individual test within a module for Deno's BDD test runner.
@@ -59,7 +57,7 @@ export default function test(
59
57
  return;
60
58
  }
61
59
 
62
- const targetTestContent = (testContent ? testContent : runtimeOptions) as (assert: Assert, meta: { testName: string; options: unknown }) => void | Promise<void>;
60
+ const targetTestContent = (testContent ?? runtimeOptions) as (assert: Assert, meta: { testName: string; options: unknown }) => void | Promise<void>;
63
61
  const context = new TestContext(testName, moduleContext);
64
62
 
65
63
  // Each test gets a fresh plain object inheriting from the module's user context.
@@ -69,38 +67,38 @@ export default function test(
69
67
  context.userContext = userContext;
70
68
 
71
69
  it(testName, { ...targetRuntimeOptions }, function () {
72
- return new Promise((resolve, reject) => {
73
- context.rejectTimeout = reject;
70
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
71
+ context.rejectTimeout = reject;
74
72
 
75
- (async () => {
76
- const hookMeta = { context: userContext };
73
+ const hookMeta = { context: userContext };
77
74
 
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
- }
75
+ (async () => {
76
+ try {
77
+ for (const mod of context.module!.moduleChain) {
78
+ for (const hook of mod.beforeEachHooks) {
79
+ await hook.call(userContext, context.assert!, hookMeta);
83
80
  }
81
+ }
84
82
 
85
- const result = await targetTestContent.call(userContext, context.assert!, { testName, options: runtimeOptions, context: userContext });
83
+ await targetTestContent.call(userContext, context.assert!, { testName, options: targetRuntimeOptions, context: userContext });
86
84
 
87
- await context.assert!.waitForAsyncOps();
85
+ await context.assert!.waitForAsyncOps();
88
86
 
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
- }
87
+ for (const mod of context.module!.moduleChain.toReversed()) {
88
+ for (const hook of mod.afterEachHooks.toReversed()) {
89
+ await hook.call(userContext, context.assert!, hookMeta);
94
90
  }
95
-
96
- resolve(result);
97
- } catch (err) {
98
- reject(err);
99
- } finally {
100
- context.clearTimeoutHandle();
101
91
  }
102
- })();
103
- });
92
+
93
+ resolve();
94
+ } catch (err) {
95
+ reject(err);
96
+ } finally {
97
+ context.clearTimeoutHandle();
98
+ }
99
+ })();
100
+
101
+ return promise;
104
102
  });
105
103
  }
106
104
 
@@ -146,3 +144,6 @@ test.skip = function skipTest(testName: string, _testContent?: unknown): void {
146
144
  test.todo = function todoTest(testName: string, _testContent?: unknown): void {
147
145
  it(testName, { ignore: true }, async function () {});
148
146
  };
147
+
148
+ export type { Assert };
149
+ export type { PushResultInfo } from '../types.ts';