@hg-ts/tests 0.5.17 → 0.5.18

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hg-ts/tests",
3
- "version": "0.5.17",
3
+ "version": "0.5.18",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -24,9 +24,9 @@
24
24
  "unplugin-swc": "1.5.9"
25
25
  },
26
26
  "devDependencies": {
27
- "@hg-ts-config/typescript": "0.5.17",
28
- "@hg-ts/linter": "0.5.17",
29
- "@hg-ts/types": "0.5.17",
27
+ "@hg-ts-config/typescript": "0.5.18",
28
+ "@hg-ts/linter": "0.5.18",
29
+ "@hg-ts/types": "0.5.18",
30
30
  "@types/node": "22.19.1",
31
31
  "@vitest/coverage-v8": "4.0.14",
32
32
  "eslint": "9.18.0",
@@ -0,0 +1,22 @@
1
+ type TestDecorator = (callback: () => Promise<void>) => Promise<void>;
2
+
3
+ export function createTestDecorator(decorator: TestDecorator): MethodDecorator {
4
+ function result(
5
+ _target: unknown,
6
+ _key: string,
7
+ descriptor: TypedPropertyDescriptor<(...args: unknown[]) => Promise<void>>,
8
+ ): TypedPropertyDescriptor<(...args: unknown[]) => Promise<void>> {
9
+ return {
10
+ async value(...args: unknown[]): Promise<void> {
11
+ // eslint-disable-next-line @typescript/no-this-alias
12
+ const self = this;
13
+ const method = descriptor.value;
14
+ if (typeof method === 'function') {
15
+ await decorator(method.bind(self, ...args));
16
+ }
17
+ },
18
+ };
19
+ }
20
+
21
+ return result as MethodDecorator;
22
+ }
@@ -0,0 +1,10 @@
1
+ import { Runner } from '../runner.js';
2
+ import type { SuiteConstructor } from '../suite.js';
3
+ import { TestOptions } from '../types.js';
4
+
5
+ export function Describe<T extends SuiteConstructor>(options: TestOptions = {}): TypedClassDecorator<T> {
6
+ return (target: T): void => {
7
+ // eslint-disable-next-line @typescript/no-floating-promises
8
+ Runner.run(target, options);
9
+ };
10
+ }
@@ -0,0 +1,20 @@
1
+ import { expect } from 'vitest';
2
+ import { createTestDecorator } from './decorate-test.js';
3
+
4
+ export function ExpectException(error?: AnyCLass<any, any[]>): MethodDecorator {
5
+ return createTestDecorator(async(method: () => Promise<void>) => {
6
+ let isThrowError = false;
7
+
8
+ try {
9
+ await method();
10
+ } catch (err: unknown) {
11
+ isThrowError = true;
12
+
13
+ if (error) {
14
+ expect(err).toBeInstanceOf(error);
15
+ }
16
+ } finally {
17
+ expect(isThrowError).toBeTruthy();
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,3 @@
1
+ export * from './expect-error.decorator.js';
2
+ export * from './describe.decorator.js';
3
+ export * from './test.decorator.js';
@@ -0,0 +1,17 @@
1
+ import type { Suite } from '../suite.js';
2
+ import type {
3
+ MethodName,
4
+ TestOptions,
5
+ } from '../types.js';
6
+
7
+ export function Test(options: TestOptions = {}): MethodDecorator {
8
+ return (
9
+ target: Object,
10
+ propertyKey: MethodName,
11
+ ): void => {
12
+ const suite = target as Suite;
13
+
14
+ suite.testsMap ??= new Map();
15
+ suite.testsMap.set(propertyKey, options);
16
+ };
17
+ }
@@ -0,0 +1,30 @@
1
+ import { expect } from 'vitest';
2
+ import {
3
+ Describe,
4
+ ExpectException,
5
+ Test,
6
+ } from './decorators/index.js';
7
+ import { Suite } from './suite.js';
8
+
9
+ @Describe()
10
+ export class ExampleTestSuite extends Suite {
11
+ @Test()
12
+ public async success(): Promise<void> {
13
+ expect(true).toBeTruthy();
14
+ }
15
+
16
+ @ExpectException(Error)
17
+ @Test()
18
+ public async failed(): Promise<void> {
19
+ throw new Error('Failed test');
20
+ }
21
+
22
+ @Test({ todo: true })
23
+ public async todo(): Promise<void> {}
24
+
25
+ @Test({ skip: true })
26
+ public async skip(): Promise<void> {}
27
+
28
+ @Test({ only: true })
29
+ public async only(): Promise<void> {}
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import nock from 'nock';
2
+
3
+ export { Describe, Test, ExpectException } from './decorators/index.js';
4
+
5
+ export { Suite } from './suite.js';
6
+ export { expect } from 'vitest';
7
+
8
+ export { nock };
package/src/runner.ts ADDED
@@ -0,0 +1,91 @@
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeAll,
5
+ beforeEach,
6
+ describe,
7
+ SuiteAPI,
8
+ test,
9
+ vi,
10
+ } from 'vitest';
11
+ import {
12
+ Suite,
13
+ SuiteConstructor,
14
+ } from './suite.js';
15
+
16
+ import type {
17
+ MethodName,
18
+ TestMethod,
19
+ TestOptions,
20
+ } from './types.js';
21
+
22
+ export class Runner {
23
+ public static async run(
24
+ suiteConstructor: SuiteConstructor,
25
+ options: TestOptions = {},
26
+ ): Promise<void> {
27
+ let moddedDescribe: SuiteAPI = describe;
28
+
29
+ if (options.only) {
30
+ moddedDescribe = moddedDescribe.only as SuiteAPI;
31
+ }
32
+
33
+ if (options.todo) {
34
+ moddedDescribe = moddedDescribe.todo as SuiteAPI;
35
+ }
36
+
37
+ if (options.skip) {
38
+ moddedDescribe = moddedDescribe.skip as SuiteAPI;
39
+ }
40
+
41
+ moddedDescribe(
42
+ options.name ?? suiteConstructor.name,
43
+ this.describe.bind(this, suiteConstructor),
44
+ );
45
+ }
46
+
47
+ private static describe(suiteConstructor: SuiteConstructor): void {
48
+ const suite = new suiteConstructor();
49
+ suite.testsMap = suiteConstructor.prototype.testsMap;
50
+
51
+ beforeAll(async() => suite.setUp());
52
+ afterAll(async() => suite.tearDown());
53
+
54
+ beforeEach(async() => {
55
+ vi.clearAllTimers();
56
+ vi.clearAllMocks();
57
+ await suite.beforeEach();
58
+ });
59
+ afterEach(async() => suite.afterEach());
60
+
61
+ if (!suite.testsMap) {
62
+ suite.testsMap = new Map();
63
+ }
64
+
65
+ suite.testsMap.forEach((options, methodName) => this.registerTests(suite, options, methodName));
66
+ }
67
+
68
+ private static registerTests(suite: Suite, options: TestOptions, testName: MethodName): void {
69
+ const { name = testName.toString() } = options;
70
+
71
+ const method = this.getTestMethod(suite, testName);
72
+
73
+
74
+ test(name, {
75
+ only: options.only ?? false,
76
+ todo: options.todo ?? false,
77
+ skip: options.skip ?? false,
78
+ }, method);
79
+ }
80
+
81
+ private static getTestMethod(suite: Suite, methodName: MethodName): TestMethod {
82
+ const testMethod = ((suite as any)[methodName] as (...args: unknown[]) => Promise<void>).bind(suite);
83
+ Object.defineProperty(testMethod, 'name', { value: methodName });
84
+
85
+ const fieldNameWrapper: any = { [methodName]: testMethod } as const;
86
+
87
+ return async(...args: any[]): Promise<void> => {
88
+ await fieldNameWrapper[methodName](...args);
89
+ };
90
+ }
91
+ }
package/src/suite.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type {
2
+ MethodName,
3
+ TestOptions,
4
+ } from './types.js';
5
+
6
+ export type SuiteConstructor = new () => Suite;
7
+
8
+ export abstract class Suite {
9
+ public testsMap: Map<MethodName, TestOptions> | null;
10
+
11
+ public async setUp(): Promise<void> {}
12
+
13
+ public async tearDown(): Promise<void> {}
14
+
15
+ public async beforeEach(): Promise<void> {}
16
+
17
+ public async afterEach(): Promise<void> {}
18
+ }
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { TestContext } from 'node:test';
2
+
3
+ export type TestMethod = (context: TestContext) => Promise<void>;
4
+ export type MethodName = string | symbol;
5
+
6
+ export type TestOptions = {
7
+ name?: string;
8
+ only?: boolean;
9
+ skip?: boolean;
10
+ todo?: boolean;
11
+ };