@travetto/test 2.1.4 → 2.2.1
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/README.md +3 -3
- package/bin/cli-test.ts +21 -13
- package/bin/lib/run.ts +11 -2
- package/bin/test-child.ts +2 -2
- package/bin/test-direct.ts +1 -1
- package/bin/test-watch.ts +1 -1
- package/package.json +8 -8
- package/src/assert/capture.ts +3 -3
- package/src/assert/check.ts +78 -33
- package/src/assert/util.ts +5 -3
- package/src/consumer/enhancer.ts +4 -2
- package/src/consumer/registry.ts +9 -7
- package/src/consumer/types/cumulative.ts +4 -4
- package/src/consumer/types/event.ts +2 -2
- package/src/consumer/types/execution.ts +1 -1
- package/src/consumer/types/json.ts +2 -2
- package/src/consumer/types/runnable.ts +5 -5
- package/src/consumer/types/summarizer.ts +2 -2
- package/src/consumer/types/tap-summary.ts +3 -3
- package/src/consumer/types/tap.ts +7 -6
- package/src/consumer/types/xunit.ts +3 -3
- package/src/consumer/util.ts +3 -1
- package/src/decorator/suite.ts +9 -8
- package/src/decorator/test.ts +5 -5
- package/src/execute/console.ts +3 -3
- package/src/execute/executor.ts +31 -21
- package/src/execute/phase.ts +14 -9
- package/src/execute/promise.ts +6 -4
- package/src/execute/runner.ts +4 -4
- package/src/execute/util.ts +3 -3
- package/src/execute/watcher.ts +2 -3
- package/src/registry/suite.ts +15 -12
- package/src/worker/child.ts +13 -10
- package/src/worker/isolated.ts +3 -3
- package/src/worker/standard.ts +5 -6
- package/support/phase.reset.ts +1 -1
- package/support/transformer.annotate.ts +10 -9
- package/support/transformer.assert.ts +15 -16
package/README.md
CHANGED
|
@@ -88,12 +88,12 @@ let SimpleTest = class SimpleTest {
|
|
|
88
88
|
ᚕ_check_1.AssertCheck.check({ file: ᚕsrc(__filename), line: 11, text: "{ size: 20, address: { state: 'VA' } } === {}", operator: "deepStrictEqual" }, true, { size: 20, address: { state: 'VA' } }, {});
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
|
-
|
|
91
|
+
tslib_1.__decorate([
|
|
92
92
|
(0, test_1.Test)({ lines: { start: 8, end: 12, codeStart: 11 } })
|
|
93
93
|
], SimpleTest.prototype, "test", null);
|
|
94
|
-
SimpleTest =
|
|
94
|
+
SimpleTest = tslib_1.__decorate([
|
|
95
95
|
ᚕ_decorator_1.Register(),
|
|
96
|
-
(0, test_1.Suite)({ lines: {
|
|
96
|
+
(0, test_1.Suite)({ lines: {} })
|
|
97
97
|
], SimpleTest);
|
|
98
98
|
Object.defineProperty(exports, 'ᚕtrv', { configurable: true, value: true });
|
|
99
99
|
```
|
package/bin/cli-test.ts
CHANGED
|
@@ -2,32 +2,40 @@ import * as os from 'os';
|
|
|
2
2
|
import { readFileSync } from 'fs';
|
|
3
3
|
|
|
4
4
|
import { FsUtil, PathUtil, ScanFs } from '@travetto/boot';
|
|
5
|
-
import { BasePlugin } from '@travetto/cli/src/plugin-base';
|
|
5
|
+
import { BasePlugin, OptionConfig } from '@travetto/cli/src/plugin-base';
|
|
6
6
|
import { EnvInit } from '@travetto/base/bin/init';
|
|
7
7
|
|
|
8
8
|
import type { RunState } from '../src/execute/types';
|
|
9
9
|
|
|
10
10
|
const modes = ['single', 'standard'] as const;
|
|
11
11
|
|
|
12
|
+
type Options = {
|
|
13
|
+
format: OptionConfig<string>;
|
|
14
|
+
concurrency: OptionConfig<number>;
|
|
15
|
+
isolated: OptionConfig<boolean>;
|
|
16
|
+
mode: OptionConfig<'single' | 'standard'>;
|
|
17
|
+
};
|
|
18
|
+
|
|
12
19
|
/**
|
|
13
20
|
* Launch test framework and execute tests
|
|
14
21
|
*/
|
|
15
|
-
export class TestPlugin extends BasePlugin {
|
|
22
|
+
export class TestPlugin extends BasePlugin<Options> {
|
|
16
23
|
name = 'test';
|
|
17
24
|
_types: string[];
|
|
18
25
|
|
|
19
|
-
getTypes() {
|
|
26
|
+
getTypes(): string[] {
|
|
20
27
|
if (!this._types) {
|
|
21
28
|
this._types = ScanFs.scanDirSync({},
|
|
22
29
|
PathUtil.resolveUnix(__dirname, '..', 'src/consumer/types/')
|
|
23
30
|
)
|
|
24
|
-
.filter(x => x.stats
|
|
25
|
-
.map(x => readFileSync(x.file, 'utf8').match(/@Consumable[(]'([^']+)/)?.[1]
|
|
31
|
+
.filter(x => x.stats?.isFile())
|
|
32
|
+
.map(x => readFileSync(x.file, 'utf8').match(/@Consumable[(]'([^']+)/)?.[1])
|
|
33
|
+
.filter((x?: string): x is string => !!x);
|
|
26
34
|
}
|
|
27
35
|
return this._types;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
getOptions() {
|
|
38
|
+
getOptions(): Options {
|
|
31
39
|
return {
|
|
32
40
|
format: this.choiceOption({ desc: 'Output format for test results', def: 'tap', choices: this.getTypes() }),
|
|
33
41
|
concurrency: this.intOption({ desc: 'Number of tests to run concurrently', lower: 1, upper: 32, def: Math.min(4, os.cpus().length - 1) }),
|
|
@@ -36,7 +44,7 @@ export class TestPlugin extends BasePlugin {
|
|
|
36
44
|
};
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
envInit() {
|
|
47
|
+
envInit(): void {
|
|
40
48
|
EnvInit.init({
|
|
41
49
|
debug: '0',
|
|
42
50
|
set: { TRV_LOG_TIME: '0' },
|
|
@@ -49,11 +57,11 @@ export class TestPlugin extends BasePlugin {
|
|
|
49
57
|
});
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
getArgs() {
|
|
60
|
+
getArgs(): string {
|
|
53
61
|
return '[regexes...]';
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
async isFile(file: string, errorIfNot?: string) {
|
|
64
|
+
async isFile(file: string, errorIfNot?: string): Promise<true | undefined> {
|
|
57
65
|
try {
|
|
58
66
|
const stat = await FsUtil.exists(file);
|
|
59
67
|
const res = stat?.isFile();
|
|
@@ -67,12 +75,12 @@ export class TestPlugin extends BasePlugin {
|
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
async onSingle(state: Partial<RunState>, file: string) {
|
|
78
|
+
async onSingle(state: Partial<RunState>, file: string): Promise<void> {
|
|
71
79
|
await this.isFile(file, 'You must specify a proper test file to run in single mode');
|
|
72
80
|
state.mode = 'single';
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
async onStandard(state: Partial<RunState>, first: string) {
|
|
83
|
+
async onStandard(state: Partial<RunState>, first: string): Promise<void> {
|
|
76
84
|
const isFile = await this.isFile(first);
|
|
77
85
|
|
|
78
86
|
if (!first) {
|
|
@@ -95,7 +103,7 @@ export class TestPlugin extends BasePlugin {
|
|
|
95
103
|
|
|
96
104
|
const [first] = regexes;
|
|
97
105
|
|
|
98
|
-
const state:
|
|
106
|
+
const state: RunState = {
|
|
99
107
|
args: regexes,
|
|
100
108
|
mode: this.cmd.mode,
|
|
101
109
|
concurrency: +this.cmd.concurrency,
|
|
@@ -108,6 +116,6 @@ export class TestPlugin extends BasePlugin {
|
|
|
108
116
|
case 'standard': await this.onStandard(state, first); break;
|
|
109
117
|
}
|
|
110
118
|
|
|
111
|
-
await runTests(state
|
|
119
|
+
await runTests(state);
|
|
112
120
|
}
|
|
113
121
|
}
|
package/bin/lib/run.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { RunState } from '../../src/execute/types';
|
|
2
2
|
|
|
3
|
+
declare global {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
5
|
+
namespace NodeJS {
|
|
6
|
+
interface ProcessEnv {
|
|
7
|
+
TRV_TEST_DELAY?: '2s';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
13
|
* Run tests given the input state
|
|
5
14
|
* @param opts
|
|
6
15
|
*/
|
|
7
|
-
export async function runTests(opts: RunState) {
|
|
16
|
+
export async function runTests(opts: RunState): Promise<void> {
|
|
8
17
|
const { PhaseManager, Util } = await import('@travetto/base');
|
|
9
18
|
await PhaseManager.run('init', '*', ['@trv:registry/init']); // Delay registry
|
|
10
19
|
|
|
@@ -14,7 +23,7 @@ export async function runTests(opts: RunState) {
|
|
|
14
23
|
RunnerUtil.registerCleanup('runner');
|
|
15
24
|
|
|
16
25
|
if (process.env.TRV_TEST_DELAY) {
|
|
17
|
-
await Util.wait(process.env.TRV_TEST_DELAY
|
|
26
|
+
await Util.wait(process.env.TRV_TEST_DELAY);
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
try {
|
package/bin/test-child.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createWriteStream } from 'fs';
|
|
|
3
3
|
import { AppCache } from '@travetto/boot';
|
|
4
4
|
import { EnvInit } from '@travetto/base/bin/init';
|
|
5
5
|
|
|
6
|
-
export async function customLogs() {
|
|
6
|
+
export async function customLogs(): Promise<void> {
|
|
7
7
|
const { ConsoleManager } = await import('@travetto/base');
|
|
8
8
|
|
|
9
9
|
const c = new console.Console({
|
|
@@ -16,7 +16,7 @@ export async function customLogs() {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export async function main() {
|
|
19
|
+
export async function main(): Promise<void> {
|
|
20
20
|
EnvInit.init({
|
|
21
21
|
debug: '0',
|
|
22
22
|
set: { TRV_LOG_TIME: '0' },
|
package/bin/test-direct.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { EnvInit } from '@travetto/base/bin/init';
|
|
|
2
2
|
import { runTests } from './lib/run';
|
|
3
3
|
|
|
4
4
|
// Direct entry point
|
|
5
|
-
export function main(...args: string[]) {
|
|
5
|
+
export function main(...args: string[]): Promise<void> {
|
|
6
6
|
EnvInit.init({
|
|
7
7
|
debug: '0',
|
|
8
8
|
set: { TRV_LOG_TIME: '0' },
|
package/bin/test-watch.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/test",
|
|
3
3
|
"displayName": "Testing",
|
|
4
|
-
"version": "2.1
|
|
4
|
+
"version": "2.2.1",
|
|
5
5
|
"description": "Declarative test framework",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"unit-testing",
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
"directory": "module/test"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@travetto/base": "^2.1
|
|
33
|
-
"@travetto/transformer": "^2.1
|
|
34
|
-
"@travetto/registry": "^2.1
|
|
35
|
-
"@travetto/watch": "^2.1
|
|
36
|
-
"@travetto/worker": "^2.1
|
|
37
|
-
"@travetto/yaml": "^2.1
|
|
32
|
+
"@travetto/base": "^2.2.1",
|
|
33
|
+
"@travetto/transformer": "^2.2.1",
|
|
34
|
+
"@travetto/registry": "^2.2.1",
|
|
35
|
+
"@travetto/watch": "^2.2.1",
|
|
36
|
+
"@travetto/worker": "^2.2.1",
|
|
37
|
+
"@travetto/yaml": "^2.2.1"
|
|
38
38
|
},
|
|
39
39
|
"optionalPeerDependencies": {
|
|
40
|
-
"@travetto/cli": "^2.1
|
|
40
|
+
"@travetto/cli": "^2.2.1"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
package/src/assert/capture.ts
CHANGED
|
@@ -20,11 +20,11 @@ class $AssertCapture {
|
|
|
20
20
|
* @param test Test to capture for
|
|
21
21
|
* @param listener optional listener for events
|
|
22
22
|
*/
|
|
23
|
-
collector(test: TestConfig, listener?: (a: Assertion) => void) {
|
|
23
|
+
collector(test: TestConfig, listener?: (a: Assertion) => void): () => Assertion[] {
|
|
24
24
|
const assertions: Assertion[] = [];
|
|
25
25
|
|
|
26
26
|
// Emit and collect, every assertion as it occurs
|
|
27
|
-
const handler = (a: CaptureAssert) => {
|
|
27
|
+
const handler = (a: CaptureAssert): void => {
|
|
28
28
|
const assrt = {
|
|
29
29
|
...a,
|
|
30
30
|
classId: test.classId,
|
|
@@ -44,7 +44,7 @@ class $AssertCapture {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
add(a: CaptureAssert) {
|
|
47
|
+
add(a: CaptureAssert): void {
|
|
48
48
|
this.#emitter.emit('assert', a);
|
|
49
49
|
}
|
|
50
50
|
}
|
package/src/assert/check.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as assert from 'assert';
|
|
|
3
3
|
import { PathUtil } from '@travetto/boot';
|
|
4
4
|
import { Util, AppError, ClassInstance, Class } from '@travetto/base';
|
|
5
5
|
|
|
6
|
-
import { ThrowableError, TestConfig } from '../model/test';
|
|
6
|
+
import { ThrowableError, TestConfig, Assertion } from '../model/test';
|
|
7
7
|
import { AssertCapture, CaptureAssert } from './capture';
|
|
8
8
|
import { AssertUtil } from './util';
|
|
9
9
|
import { ASSERT_FN_OPERATOR, OP_MAPPING } from './types';
|
|
@@ -14,6 +14,11 @@ declare module 'assert' {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
type StringFields<T> = {
|
|
18
|
+
[K in Extract<keyof T, string>]:
|
|
19
|
+
(T[K] extends string ? K : never) // eslint-disable-line @typescript-eslint/ban-types
|
|
20
|
+
}[Extract<keyof T, string>];
|
|
21
|
+
|
|
17
22
|
/**
|
|
18
23
|
* Check assertion
|
|
19
24
|
*/
|
|
@@ -24,7 +29,7 @@ export class AssertCheck {
|
|
|
24
29
|
* @param positive Is the check positive or negative
|
|
25
30
|
* @param args The arguments passed in
|
|
26
31
|
*/
|
|
27
|
-
static check(assertion: CaptureAssert, positive: boolean, ...args: unknown[]) {
|
|
32
|
+
static check(assertion: CaptureAssert, positive: boolean, ...args: unknown[]): void {
|
|
28
33
|
let fn = assertion.operator;
|
|
29
34
|
assertion.operator = ASSERT_FN_OPERATOR[fn];
|
|
30
35
|
|
|
@@ -34,36 +39,45 @@ export class AssertCheck {
|
|
|
34
39
|
};
|
|
35
40
|
|
|
36
41
|
// Invert check for negative
|
|
37
|
-
const assertFn = positive ? assert : (x: unknown, msg?: string) => assert(!x, msg);
|
|
42
|
+
const assertFn = positive ? assert : (x: unknown, msg?: string): unknown => assert(!x, msg);
|
|
38
43
|
|
|
39
44
|
// Check fn to call
|
|
40
45
|
if (fn === 'fail') {
|
|
41
46
|
if (args.length > 1) {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
42
48
|
[assertion.actual, assertion.expected, assertion.message, assertion.operator] = args as [unknown, unknown, string, string];
|
|
43
49
|
} else {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
44
51
|
[assertion.message] = args as [string];
|
|
45
52
|
}
|
|
46
53
|
} else if (/throw|reject/i.test(fn)) {
|
|
47
54
|
assertion.operator = fn;
|
|
48
55
|
if (typeof args[1] !== 'string') {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
49
57
|
[, assertion.expected, assertion.message] = args as [unknown, unknown, string];
|
|
50
58
|
} else {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
51
60
|
[, assertion.message] = args as [unknown, string];
|
|
52
61
|
}
|
|
53
62
|
} else if (fn === 'ok' || fn === 'assert') {
|
|
54
63
|
fn = assertion.operator = 'ok';
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
55
65
|
[assertion.actual, assertion.message] = args as [unknown, string];
|
|
56
66
|
assertion.expected = { toClean: () => positive ? 'truthy' : 'falsy' };
|
|
57
67
|
common.state = 'should be';
|
|
58
68
|
} else if (fn === 'includes') {
|
|
59
69
|
assertion.operator = fn;
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
60
71
|
[assertion.expected, assertion.actual, assertion.message] = args as [unknown, unknown, string];
|
|
61
72
|
} else if (fn === 'instanceof') {
|
|
62
73
|
assertion.operator = fn;
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
63
75
|
[assertion.actual, assertion.expected, assertion.message] = args as [unknown, unknown, string];
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
64
77
|
assertion.actual = (assertion.actual as ClassInstance)?.constructor;
|
|
65
78
|
} else { // Handle unknown
|
|
66
79
|
assertion.operator = fn ?? '';
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
67
81
|
[assertion.actual, assertion.expected, assertion.message] = args as [unknown, unknown, string];
|
|
68
82
|
}
|
|
69
83
|
|
|
@@ -77,44 +91,56 @@ export class AssertCheck {
|
|
|
77
91
|
assertion.expected = AssertUtil.cleanValue(assertion.expected);
|
|
78
92
|
}
|
|
79
93
|
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
80
95
|
const [actual, expected, message] = args as [unknown, unknown, string];
|
|
81
96
|
|
|
82
97
|
// Actually run the assertion
|
|
83
98
|
switch (fn) {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
84
100
|
case 'instanceof': assertFn(actual instanceof (expected as Class), message); break;
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
85
102
|
case 'in': assertFn((actual as string) in (expected as object), message); break;
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
86
104
|
case 'lessThan': assertFn((actual as number) < (expected as number), message); break;
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
87
106
|
case 'lessThanEqual': assertFn((actual as number) <= (expected as number), message); break;
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
88
108
|
case 'greaterThan': assertFn((actual as number) > (expected as number), message); break;
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
89
110
|
case 'greaterThanEqual': assertFn((actual as number) >= (expected as number), message); break;
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
90
112
|
case 'ok': assertFn.apply(null, args as [unknown, string]); break; // eslint-disable-line prefer-spread
|
|
91
113
|
default:
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
92
115
|
if (fn && assert[fn as keyof typeof assert]) { // Assert call
|
|
93
116
|
if (/not/i.test(fn)) {
|
|
94
117
|
common.state = 'should not';
|
|
95
118
|
}
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
96
120
|
assert[fn as 'ok'].apply(null, args as [boolean, string | undefined]);
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
97
122
|
} else if (expected && !!(expected as Record<string, Function>)[fn]) { // Dotted Method call (e.g. assert.rejects)
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
98
124
|
assertFn((expected as typeof assert)[fn as 'ok'](actual));
|
|
99
125
|
}
|
|
100
126
|
}
|
|
101
127
|
|
|
102
128
|
// Pushing on not error
|
|
103
129
|
AssertCapture.add(assertion);
|
|
104
|
-
} catch (
|
|
130
|
+
} catch (err) {
|
|
105
131
|
// On error, produce the appropriate error message
|
|
106
|
-
if (
|
|
132
|
+
if (err instanceof assert.AssertionError) {
|
|
107
133
|
if (!assertion.message) {
|
|
108
134
|
assertion.message = (OP_MAPPING[fn] ?? '{state} be {expected}');
|
|
109
135
|
}
|
|
110
136
|
assertion.message = assertion.message
|
|
111
|
-
.replace(/[{]([A-Za-z]+)[}]/g, (a, k) => common[k] || assertion[k
|
|
137
|
+
.replace(/[{]([A-Za-z]+)[}]/g, (a, k: StringFields<Assertion>) => common[k] || assertion[k]!)
|
|
112
138
|
.replace(/not not/g, ''); // Handle double negatives
|
|
113
|
-
assertion.error =
|
|
114
|
-
|
|
139
|
+
assertion.error = err;
|
|
140
|
+
err.message = assertion.message;
|
|
115
141
|
AssertCapture.add(assertion);
|
|
116
142
|
}
|
|
117
|
-
throw
|
|
143
|
+
throw err;
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
|
|
@@ -158,6 +184,30 @@ export class AssertCheck {
|
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
186
|
|
|
187
|
+
static #onError(
|
|
188
|
+
positive: boolean,
|
|
189
|
+
message: string | undefined,
|
|
190
|
+
err: unknown, missed: Error | undefined,
|
|
191
|
+
shouldThrow: ThrowableError | undefined,
|
|
192
|
+
assertion: CaptureAssert
|
|
193
|
+
): void {
|
|
194
|
+
if (Util.isPrimitive(err)) {
|
|
195
|
+
err = new Error(`${err}`);
|
|
196
|
+
}
|
|
197
|
+
if (!(err instanceof Error)) {
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
if (positive) {
|
|
201
|
+
missed = new AppError('Error thrown, but expected no errors', 'general', {}, err.stack);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const resolvedErr = (missed && err) ?? this.checkError(shouldThrow, err);
|
|
205
|
+
if (resolvedErr) {
|
|
206
|
+
assertion.message = message || missed?.message || resolvedErr.message;
|
|
207
|
+
throw (assertion.error = resolvedErr);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
161
211
|
/**
|
|
162
212
|
* Check the throw, doesNotThrow behavior of an assertion
|
|
163
213
|
* @param assertion The basic assertion information
|
|
@@ -166,7 +216,13 @@ export class AssertCheck {
|
|
|
166
216
|
* @param shouldThrow Should this action throw
|
|
167
217
|
* @param message Message to share on failure
|
|
168
218
|
*/
|
|
169
|
-
static checkThrow(
|
|
219
|
+
static checkThrow(
|
|
220
|
+
assertion: CaptureAssert,
|
|
221
|
+
positive: boolean,
|
|
222
|
+
action: Function,
|
|
223
|
+
shouldThrow?: ThrowableError,
|
|
224
|
+
message?: string
|
|
225
|
+
): void {
|
|
170
226
|
let missed: Error | undefined;
|
|
171
227
|
|
|
172
228
|
try {
|
|
@@ -177,17 +233,8 @@ export class AssertCheck {
|
|
|
177
233
|
}
|
|
178
234
|
throw (missed = new AppError(`No error thrown, but expected ${shouldThrow ?? 'an error'}`));
|
|
179
235
|
}
|
|
180
|
-
} catch (
|
|
181
|
-
|
|
182
|
-
missed = new AppError('Error thrown, but expected no errors');
|
|
183
|
-
missed.stack = e.stack;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
e = (missed && e) || this.checkError(shouldThrow, e);
|
|
187
|
-
if (e) {
|
|
188
|
-
assertion.message = message || missed?.message || e.message;
|
|
189
|
-
throw (assertion.error = e);
|
|
190
|
-
}
|
|
236
|
+
} catch (err) {
|
|
237
|
+
this.#onError(positive, message, err, missed, shouldThrow, assertion);
|
|
191
238
|
} finally {
|
|
192
239
|
AssertCapture.add(assertion);
|
|
193
240
|
}
|
|
@@ -201,7 +248,13 @@ export class AssertCheck {
|
|
|
201
248
|
* @param shouldThrow Should this action reject
|
|
202
249
|
* @param message Message to share on failure
|
|
203
250
|
*/
|
|
204
|
-
static async checkThrowAsync(
|
|
251
|
+
static async checkThrowAsync(
|
|
252
|
+
assertion: CaptureAssert,
|
|
253
|
+
positive: boolean,
|
|
254
|
+
action: Function | Promise<unknown>,
|
|
255
|
+
shouldThrow?: ThrowableError,
|
|
256
|
+
message?: string
|
|
257
|
+
): Promise<void> {
|
|
205
258
|
let missed: Error | undefined;
|
|
206
259
|
|
|
207
260
|
try {
|
|
@@ -216,16 +269,8 @@ export class AssertCheck {
|
|
|
216
269
|
}
|
|
217
270
|
throw (missed = new AppError(`No error thrown, but expected ${shouldThrow ?? 'an error'} `));
|
|
218
271
|
}
|
|
219
|
-
} catch (
|
|
220
|
-
|
|
221
|
-
missed = new AppError('Error thrown, but expected no errors');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
e = (missed && e) || this.checkError(shouldThrow, e);
|
|
225
|
-
if (e) {
|
|
226
|
-
assertion.message = message || missed?.message || e.message;
|
|
227
|
-
throw (assertion.error = e);
|
|
228
|
-
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
this.#onError(positive, message, err, missed, shouldThrow, assertion);
|
|
229
274
|
} finally {
|
|
230
275
|
AssertCapture.add(assertion);
|
|
231
276
|
}
|
|
@@ -234,7 +279,7 @@ export class AssertCheck {
|
|
|
234
279
|
/**
|
|
235
280
|
* Look for any unhandled exceptions
|
|
236
281
|
*/
|
|
237
|
-
static checkUnhandled(test: TestConfig, err: Error | assert.AssertionError) {
|
|
282
|
+
static checkUnhandled(test: TestConfig, err: Error | assert.AssertionError): void {
|
|
238
283
|
let line = AssertUtil.getPositionOfError(err, test.file).line;
|
|
239
284
|
if (line === 1) {
|
|
240
285
|
line = test.lines.start;
|
package/src/assert/util.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { TestConfig, Assertion, TestResult } from '../model/test';
|
|
|
7
7
|
import { SuiteConfig } from '../model/suite';
|
|
8
8
|
|
|
9
9
|
function isCleanable(o: unknown): o is { toClean(): unknown } {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
10
11
|
return !!o && !!(o as { toClean: unknown }).toClean;
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -17,7 +18,7 @@ export class AssertUtil {
|
|
|
17
18
|
/**
|
|
18
19
|
* Clean a value for displaying in the output
|
|
19
20
|
*/
|
|
20
|
-
static cleanValue(val: unknown) {
|
|
21
|
+
static cleanValue(val: unknown): unknown {
|
|
21
22
|
if (isCleanable(val)) {
|
|
22
23
|
return val.toClean();
|
|
23
24
|
} else if (val === null || val === undefined
|
|
@@ -26,6 +27,7 @@ export class AssertUtil {
|
|
|
26
27
|
) {
|
|
27
28
|
return JSON.stringify(val);
|
|
28
29
|
} else {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
29
31
|
const subV = val as (Class | ClassInstance);
|
|
30
32
|
if (subV.ᚕid || !subV.constructor || (!subV.constructor.ᚕid && Util.isFunction(subV))) { // If a function, show name
|
|
31
33
|
return subV.name;
|
|
@@ -38,7 +40,7 @@ export class AssertUtil {
|
|
|
38
40
|
/**
|
|
39
41
|
* Determine file location for a given error and the stack trace
|
|
40
42
|
*/
|
|
41
|
-
static getPositionOfError(err: Error, filename: string) {
|
|
43
|
+
static getPositionOfError(err: Error, filename: string): { file: string, line: number } {
|
|
42
44
|
const lines = (err.stack ?? new Error().stack!)
|
|
43
45
|
.replace(/[\\]/g, '/')
|
|
44
46
|
.split('\n')
|
|
@@ -82,7 +84,7 @@ export class AssertUtil {
|
|
|
82
84
|
/**
|
|
83
85
|
* Generate a suite error given a suite config, and an error
|
|
84
86
|
*/
|
|
85
|
-
static generateSuiteError(suite: SuiteConfig, methodName: string, error: Error) {
|
|
87
|
+
static generateSuiteError(suite: SuiteConfig, methodName: string, error: Error): { assert: Assertion, testResult: TestResult, testConfig: TestConfig } {
|
|
86
88
|
const { file, ...pos } = this.getPositionOfError(error, suite.file);
|
|
87
89
|
let line = pos.line;
|
|
88
90
|
|
package/src/consumer/enhancer.ts
CHANGED
|
@@ -20,5 +20,7 @@ export type TestResultsEnhancer = typeof COLOR_ENHANCER;
|
|
|
20
20
|
/**
|
|
21
21
|
* Dummy enhancer does nothing
|
|
22
22
|
*/
|
|
23
|
-
export const DUMMY_ENHANCER =
|
|
24
|
-
.
|
|
23
|
+
export const DUMMY_ENHANCER: TestResultsEnhancer = [
|
|
24
|
+
Object.keys(COLOR_ENHANCER)
|
|
25
|
+
.reduce<Partial<TestResultsEnhancer>>((acc, k) => (acc[k] = (x: unknown): string => `${x}`) && acc, {})
|
|
26
|
+
].filter((x): x is TestResultsEnhancer => !!x)[0];
|
package/src/consumer/registry.ts
CHANGED
|
@@ -9,9 +9,9 @@ class $TestConsumerRegistry {
|
|
|
9
9
|
#primary: Class<TestConsumer>;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Manual initialization when running
|
|
12
|
+
* Manual initialization when running outside of the bootstrap process
|
|
13
13
|
*/
|
|
14
|
-
async manualInit() {
|
|
14
|
+
async manualInit(): Promise<void> {
|
|
15
15
|
await import('./types/index');
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ class $TestConsumerRegistry {
|
|
|
21
21
|
* @param cls The consumer class
|
|
22
22
|
* @param isDefault Set as the default consumer
|
|
23
23
|
*/
|
|
24
|
-
add(type: string, cls: Class<TestConsumer>, isDefault = false) {
|
|
24
|
+
add(type: string, cls: Class<TestConsumer>, isDefault = false): void {
|
|
25
25
|
if (isDefault) {
|
|
26
26
|
this.#primary = cls;
|
|
27
27
|
}
|
|
@@ -32,8 +32,8 @@ class $TestConsumerRegistry {
|
|
|
32
32
|
* Retrieve a registered consumer
|
|
33
33
|
* @param type The unique identifier
|
|
34
34
|
*/
|
|
35
|
-
get(type: string) {
|
|
36
|
-
return this.#registered.get(type)
|
|
35
|
+
get(type: string): Class<TestConsumer> {
|
|
36
|
+
return this.#registered.get(type)!;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -42,6 +42,7 @@ class $TestConsumerRegistry {
|
|
|
42
42
|
*/
|
|
43
43
|
getInstance(consumer: string | TestConsumer): TestConsumer {
|
|
44
44
|
return typeof consumer === 'string' ?
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
45
46
|
new ((this.get(consumer) ?? this.#primary) as ConcreteClass)() :
|
|
46
47
|
consumer;
|
|
47
48
|
}
|
|
@@ -54,8 +55,9 @@ export const TestConsumerRegistry = new $TestConsumerRegistry();
|
|
|
54
55
|
* @param type The unique identifier for the consumer
|
|
55
56
|
* @param isDefault Is this the default consumer. Last one wins
|
|
56
57
|
*/
|
|
57
|
-
export function Consumable(type: string, isDefault = false) {
|
|
58
|
+
export function Consumable(type: string, isDefault = false): ClassDecorator {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
58
60
|
return function (cls: Class<TestConsumer>) {
|
|
59
61
|
TestConsumerRegistry.add(type, cls, isDefault);
|
|
60
|
-
};
|
|
62
|
+
} as ClassDecorator;
|
|
61
63
|
}
|
|
@@ -26,7 +26,7 @@ export class CumulativeSummaryConsumer implements TestConsumer {
|
|
|
26
26
|
*/
|
|
27
27
|
summarizeSuite(test: TestResult): SuiteResult {
|
|
28
28
|
try {
|
|
29
|
-
// TODO: Load
|
|
29
|
+
// TODO: Load asynchronously
|
|
30
30
|
require(test.file);
|
|
31
31
|
this.#state[test.classId] = this.#state[test.classId] ?? {};
|
|
32
32
|
this.#state[test.classId][test.methodName] = test.status;
|
|
@@ -42,7 +42,7 @@ export class CumulativeSummaryConsumer implements TestConsumer {
|
|
|
42
42
|
/**
|
|
43
43
|
* Remove a class
|
|
44
44
|
*/
|
|
45
|
-
removeClass(clsId: string) {
|
|
45
|
+
removeClass(clsId: string): SuiteResult {
|
|
46
46
|
this.#state[clsId] = {};
|
|
47
47
|
return {
|
|
48
48
|
classId: clsId, passed: 0, failed: 0, skipped: 0, total: 0, tests: [], duration: 0, file: '', lines: { start: 0, end: 0 }
|
|
@@ -52,7 +52,7 @@ export class CumulativeSummaryConsumer implements TestConsumer {
|
|
|
52
52
|
/**
|
|
53
53
|
* Compute totals
|
|
54
54
|
*/
|
|
55
|
-
computeTotal(cls: Class) {
|
|
55
|
+
computeTotal(cls: Class): SuiteResult {
|
|
56
56
|
const suite = SuiteRegistry.get(cls);
|
|
57
57
|
const total = suite.tests.reduce((acc, x) => {
|
|
58
58
|
const status = this.#state[x.classId][x.methodName] ?? 'unknown';
|
|
@@ -77,7 +77,7 @@ export class CumulativeSummaryConsumer implements TestConsumer {
|
|
|
77
77
|
* Listen for event, process the full event, and if the event is an after test,
|
|
78
78
|
* send a full suite summary
|
|
79
79
|
*/
|
|
80
|
-
onEvent(e: TestEvent) {
|
|
80
|
+
onEvent(e: TestEvent): void {
|
|
81
81
|
this.#target.onEvent(e);
|
|
82
82
|
try {
|
|
83
83
|
if (e.type === 'test' && e.phase === 'after') {
|
|
@@ -6,7 +6,7 @@ import { ConsumerUtil } from '../util';
|
|
|
6
6
|
import { Consumable } from '../registry';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Streams all test events a JSON payload, in an
|
|
9
|
+
* Streams all test events a JSON payload, in an nd-json format
|
|
10
10
|
*/
|
|
11
11
|
@Consumable('event')
|
|
12
12
|
export class EventStreamer implements TestConsumer {
|
|
@@ -16,7 +16,7 @@ export class EventStreamer implements TestConsumer {
|
|
|
16
16
|
this.#stream = stream;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
onEvent(event: TestEvent) {
|
|
19
|
+
onEvent(event: TestEvent): void {
|
|
20
20
|
const out = { ...event };
|
|
21
21
|
ConsumerUtil.serializeErrors(out);
|
|
22
22
|
this.#stream.write(`${JSON.stringify(out)}\n`);
|
|
@@ -11,7 +11,7 @@ import { Consumable } from '../registry';
|
|
|
11
11
|
@Consumable('exec')
|
|
12
12
|
export class ExecutionEmitter extends ChildCommChannel<TestEvent> implements TestConsumer {
|
|
13
13
|
|
|
14
|
-
onEvent(event: TestEvent) {
|
|
14
|
+
onEvent(event: TestEvent): void {
|
|
15
15
|
const out = { ...event };
|
|
16
16
|
ConsumerUtil.serializeErrors(out);
|
|
17
17
|
this.send(event.type, out);
|
|
@@ -16,9 +16,9 @@ export class JSONEmitter implements TestConsumer {
|
|
|
16
16
|
this.#stream = stream;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
onEvent(event: TestEvent) { }
|
|
19
|
+
onEvent(event: TestEvent): void { }
|
|
20
20
|
|
|
21
|
-
onSummary(summary: SuitesSummary) {
|
|
21
|
+
onSummary(summary: SuitesSummary): void {
|
|
22
22
|
this.#stream.write(JSON.stringify(summary, undefined, 2));
|
|
23
23
|
}
|
|
24
24
|
}
|