@travetto/test 5.0.0-rc.7 → 5.0.0-rc.9
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 +7 -7
- package/src/assert/capture.ts +3 -3
- package/src/assert/check.ts +22 -24
- package/src/assert/util.ts +1 -3
- package/src/consumer/error.ts +4 -6
- package/src/consumer/registry.ts +2 -3
- package/src/consumer/types/tap-streamed.ts +2 -2
- package/src/decorator/suite.ts +4 -5
- package/src/execute/executor.ts +5 -7
- package/src/execute/promise.ts +3 -3
- package/src/registry/suite.ts +4 -7
- package/support/transformer.assert.ts +8 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/test",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.9",
|
|
4
4
|
"description": "Declarative test framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"unit-testing",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"directory": "module/test"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/runtime": "^5.0.0-rc.
|
|
31
|
-
"@travetto/registry": "^5.0.0-rc.
|
|
32
|
-
"@travetto/terminal": "^5.0.0-rc.
|
|
33
|
-
"@travetto/worker": "^5.0.0-rc.
|
|
30
|
+
"@travetto/runtime": "^5.0.0-rc.9",
|
|
31
|
+
"@travetto/registry": "^5.0.0-rc.9",
|
|
32
|
+
"@travetto/terminal": "^5.0.0-rc.9",
|
|
33
|
+
"@travetto/worker": "^5.0.0-rc.9",
|
|
34
34
|
"yaml": "^2.4.5"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@travetto/cli": "^5.0.0-rc.
|
|
38
|
-
"@travetto/transformer": "^5.0.0-rc.
|
|
37
|
+
"@travetto/cli": "^5.0.0-rc.9",
|
|
38
|
+
"@travetto/transformer": "^5.0.0-rc.6"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@travetto/transformer": {
|
package/src/assert/capture.ts
CHANGED
|
@@ -26,15 +26,15 @@ class $AssertCapture {
|
|
|
26
26
|
|
|
27
27
|
// Emit and collect, every assertion as it occurs
|
|
28
28
|
const handler = (a: CaptureAssert): void => {
|
|
29
|
-
const
|
|
29
|
+
const asrt: Assertion = {
|
|
30
30
|
...a,
|
|
31
31
|
import: a.import ?? a.module!.join('/'),
|
|
32
32
|
classId: test.classId,
|
|
33
33
|
methodName: test.methodName
|
|
34
34
|
};
|
|
35
|
-
assertions.push(
|
|
35
|
+
assertions.push(asrt);
|
|
36
36
|
if (listener) {
|
|
37
|
-
listener(
|
|
37
|
+
listener(asrt);
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
|
package/src/assert/check.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import { AppError,
|
|
3
|
+
import { AppError, Class, castTo, castKey, asConstructable } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import { ThrowableError, TestConfig, Assertion } from '../model/test';
|
|
6
6
|
import { AssertCapture, CaptureAssert } from './capture';
|
|
@@ -34,36 +34,35 @@ export class AssertCheck {
|
|
|
34
34
|
// Invert check for negative
|
|
35
35
|
const assertFn = positive ? assert : (x: unknown, msg?: string): unknown => assert(!x, msg);
|
|
36
36
|
|
|
37
|
-
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
38
37
|
// Check fn to call
|
|
39
38
|
if (fn === 'fail') {
|
|
40
39
|
if (args.length > 1) {
|
|
41
|
-
[assertion.actual, assertion.expected, assertion.message, assertion.operator] = args
|
|
40
|
+
[assertion.actual, assertion.expected, assertion.message, assertion.operator] = castTo(args);
|
|
42
41
|
} else {
|
|
43
|
-
[assertion.message] = args
|
|
42
|
+
[assertion.message] = castTo(args);
|
|
44
43
|
}
|
|
45
44
|
} else if (/throw|reject/i.test(fn)) {
|
|
46
45
|
assertion.operator = fn;
|
|
47
46
|
if (typeof args[1] !== 'string') {
|
|
48
|
-
[, assertion.expected, assertion.message] = args
|
|
47
|
+
[, assertion.expected, assertion.message] = castTo(args);
|
|
49
48
|
} else {
|
|
50
|
-
[, assertion.message] = args
|
|
49
|
+
[, assertion.message] = castTo(args);
|
|
51
50
|
}
|
|
52
51
|
} else if (fn === 'ok' || fn === 'assert') {
|
|
53
52
|
fn = assertion.operator = 'ok';
|
|
54
|
-
[assertion.actual, assertion.message] = args
|
|
53
|
+
[assertion.actual, assertion.message] = castTo(args);
|
|
55
54
|
assertion.expected = { toClean: (): string => positive ? 'truthy' : 'falsy' };
|
|
56
55
|
common.state = 'should be';
|
|
57
56
|
} else if (fn === 'includes') {
|
|
58
57
|
assertion.operator = fn;
|
|
59
|
-
[assertion.actual, assertion.expected, assertion.message] = args
|
|
58
|
+
[assertion.actual, assertion.expected, assertion.message] = castTo(args);
|
|
60
59
|
} else if (fn === 'instanceof') {
|
|
61
60
|
assertion.operator = fn;
|
|
62
|
-
[assertion.actual, assertion.expected, assertion.message] = args
|
|
63
|
-
assertion.actual = (assertion.actual
|
|
61
|
+
[assertion.actual, assertion.expected, assertion.message] = castTo(args);
|
|
62
|
+
assertion.actual = asConstructable(assertion.actual)?.constructor;
|
|
64
63
|
} else { // Handle unknown
|
|
65
64
|
assertion.operator = fn ?? '';
|
|
66
|
-
[assertion.actual, assertion.expected, assertion.message] = args
|
|
65
|
+
[assertion.actual, assertion.expected, assertion.message] = castTo(args);
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
try {
|
|
@@ -76,25 +75,25 @@ export class AssertCheck {
|
|
|
76
75
|
assertion.expected = AssertUtil.cleanValue(assertion.expected);
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
const [actual, expected, message]
|
|
78
|
+
const [actual, expected, message]: [unknown, unknown, string] = castTo(args);
|
|
80
79
|
|
|
81
80
|
// Actually run the assertion
|
|
82
81
|
switch (fn) {
|
|
83
|
-
case 'includes': assertFn(
|
|
84
|
-
case 'test': assertFn((expected
|
|
85
|
-
case 'instanceof': assertFn(actual instanceof (expected
|
|
86
|
-
case 'in': assertFn((actual
|
|
87
|
-
case 'lessThan': assertFn((actual
|
|
88
|
-
case 'lessThanEqual': assertFn((actual
|
|
89
|
-
case 'greaterThan': assertFn((actual
|
|
90
|
-
case 'greaterThanEqual': assertFn((actual
|
|
91
|
-
case 'ok': assertFn(...args
|
|
82
|
+
case 'includes': assertFn(castTo<unknown[]>(actual).includes(expected), message); break;
|
|
83
|
+
case 'test': assertFn(castTo<RegExp>(expected).test(castTo(actual)), message); break;
|
|
84
|
+
case 'instanceof': assertFn(actual instanceof castTo<Class>(expected), message); break;
|
|
85
|
+
case 'in': assertFn(castTo<string>(actual) in castTo<object>(expected), message); break;
|
|
86
|
+
case 'lessThan': assertFn(castTo<number>(actual) < castTo<number>(expected), message); break;
|
|
87
|
+
case 'lessThanEqual': assertFn(castTo<number>(actual) <= castTo<number>(expected), message); break;
|
|
88
|
+
case 'greaterThan': assertFn(castTo<number>(actual) > castTo<number>(expected), message); break;
|
|
89
|
+
case 'greaterThanEqual': assertFn(castTo<number>(actual) >= castTo<number>(expected), message); break;
|
|
90
|
+
case 'ok': assertFn(...castTo<Parameters<typeof assertFn>>(args)); break;
|
|
92
91
|
default:
|
|
93
|
-
if (fn && assert[
|
|
92
|
+
if (fn && assert[castKey<typeof assert>(fn)]) { // Assert call
|
|
94
93
|
if (/not/i.test(fn)) {
|
|
95
94
|
common.state = 'should not';
|
|
96
95
|
}
|
|
97
|
-
assert[
|
|
96
|
+
assert[castTo<'ok'>(fn)].apply(null, castTo(args));
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
99
|
|
|
@@ -115,7 +114,6 @@ export class AssertCheck {
|
|
|
115
114
|
}
|
|
116
115
|
throw err;
|
|
117
116
|
}
|
|
118
|
-
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
/**
|
package/src/assert/util.ts
CHANGED
|
@@ -6,8 +6,7 @@ import { TestConfig, Assertion, TestResult } from '../model/test';
|
|
|
6
6
|
import { SuiteConfig } from '../model/suite';
|
|
7
7
|
|
|
8
8
|
function isCleanable(o: unknown): o is { toClean(): unknown } {
|
|
9
|
-
|
|
10
|
-
return !!o && !!(o as { toClean: unknown }).toClean;
|
|
9
|
+
return !!o && typeof o === 'object' && 'toClean' in o && typeof o.toClean === 'function';
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -35,7 +34,6 @@ export class AssertUtil {
|
|
|
35
34
|
break;
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
39
37
|
return util.inspect(val, false, 1).replace(/\n/g, ' ');
|
|
40
38
|
}
|
|
41
39
|
|
package/src/consumer/error.ts
CHANGED
|
@@ -14,9 +14,9 @@ export class ErrorUtil {
|
|
|
14
14
|
/**
|
|
15
15
|
* Prepare error for transmission
|
|
16
16
|
*/
|
|
17
|
-
static serializeError(e: Error | SerializedError):
|
|
17
|
+
static serializeError(e: Error | SerializedError): Error;
|
|
18
18
|
static serializeError(e: undefined): undefined;
|
|
19
|
-
static serializeError(e: Error | SerializedError | undefined):
|
|
19
|
+
static serializeError(e: Error | SerializedError | undefined): Error | undefined {
|
|
20
20
|
let error: SerializedError | undefined;
|
|
21
21
|
|
|
22
22
|
if (e) {
|
|
@@ -66,13 +66,11 @@ export class ErrorUtil {
|
|
|
66
66
|
if (out.phase === 'after') {
|
|
67
67
|
if (out.type === 'test') {
|
|
68
68
|
if (out.test.error) {
|
|
69
|
-
|
|
70
|
-
out.test.error = this.serializeError(out.test.error) as Error;
|
|
69
|
+
out.test.error = this.serializeError(out.test.error);
|
|
71
70
|
}
|
|
72
71
|
} else if (out.type === 'assertion') {
|
|
73
72
|
if (out.assertion.error) {
|
|
74
|
-
|
|
75
|
-
out.assertion.error = this.serializeError(out.assertion.error) as Error;
|
|
73
|
+
out.assertion.error = this.serializeError(out.assertion.error);
|
|
76
74
|
}
|
|
77
75
|
}
|
|
78
76
|
}
|
package/src/consumer/registry.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { classConstruct, type Class } from '@travetto/runtime';
|
|
2
2
|
import { TestConsumer } from './types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -45,8 +45,7 @@ class $TestConsumerRegistry {
|
|
|
45
45
|
await this.manualInit();
|
|
46
46
|
|
|
47
47
|
return typeof consumer === 'string' ?
|
|
48
|
-
|
|
49
|
-
new ((this.get(consumer) ?? this.#primary) as ConcreteClass)() :
|
|
48
|
+
classConstruct(this.get(consumer) ?? this.#primary) :
|
|
50
49
|
consumer;
|
|
51
50
|
}
|
|
52
51
|
}
|
|
@@ -30,7 +30,7 @@ export class TapStreamedEmitter implements TestConsumer {
|
|
|
30
30
|
this.#consumer.onStart();
|
|
31
31
|
|
|
32
32
|
let failed = 0;
|
|
33
|
-
const
|
|
33
|
+
const success = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green
|
|
34
34
|
const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red
|
|
35
35
|
this.#progress = this.#terminal.streamToBottom(
|
|
36
36
|
Util.mapAsyncItr(
|
|
@@ -39,7 +39,7 @@ export class TapStreamedEmitter implements TestConsumer {
|
|
|
39
39
|
failed += (value.status === 'failed' ? 1 : 0);
|
|
40
40
|
return { value: `Tests %idx/%total [${failed} failed] -- ${value.classId}`, total: state.testCount, idx };
|
|
41
41
|
},
|
|
42
|
-
TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail :
|
|
42
|
+
TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail : success }) })
|
|
43
43
|
),
|
|
44
44
|
{ minDelay: 100 }
|
|
45
45
|
);
|
package/src/decorator/suite.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class, ClassInstance, describeFunction } from '@travetto/runtime';
|
|
1
|
+
import { castTo, Class, ClassInstance, describeFunction } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { SuiteRegistry } from '../registry/suite';
|
|
4
4
|
import { SuiteConfig } from '../model/suite';
|
|
@@ -23,17 +23,16 @@ export function Suite(description?: string | Partial<SuiteConfig>, ...rest: Part
|
|
|
23
23
|
Object.assign(extra, r);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
const decorator = ((target: Class) => {
|
|
26
|
+
const dec = (target: Class): typeof target => {
|
|
28
27
|
const cfg = { description: descriptionString, ...extra };
|
|
29
28
|
if (describeFunction(target).abstract) {
|
|
30
29
|
cfg.skip = true;
|
|
31
30
|
}
|
|
32
31
|
SuiteRegistry.register(target, cfg);
|
|
33
32
|
return target;
|
|
34
|
-
}
|
|
33
|
+
};
|
|
35
34
|
|
|
36
|
-
return
|
|
35
|
+
return castTo(dec);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
function listener(phase: SuitePhase) {
|
package/src/execute/executor.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AssertionError } from 'node:assert';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { Env, TimeUtil, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
4
|
+
import { Env, TimeUtil, Runtime, RuntimeIndex, castTo, asFull, Class } from '@travetto/runtime';
|
|
5
5
|
import { Barrier, ExecutionError } from '@travetto/worker';
|
|
6
6
|
|
|
7
7
|
import { SuiteRegistry } from '../registry/suite';
|
|
@@ -39,8 +39,7 @@ export class TestExecutor {
|
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
41
|
await pCap.run(() =>
|
|
42
|
-
|
|
43
|
-
(suite.instance as Record<string, Function>)[test.methodName]()
|
|
42
|
+
castTo<Record<string, Function>>(suite.instance)[test.methodName]()
|
|
44
43
|
);
|
|
45
44
|
} finally {
|
|
46
45
|
process.env = env; // Restore
|
|
@@ -68,8 +67,7 @@ export class TestExecutor {
|
|
|
68
67
|
static failFile(consumer: TestConsumer, imp: string, err: Error): void {
|
|
69
68
|
const name = path.basename(imp);
|
|
70
69
|
const classId = `${RuntimeIndex.getFromImport(imp)?.id}○${name}`;
|
|
71
|
-
|
|
72
|
-
const suite = { class: { name }, classId, duration: 0, lineStart: 1, lineEnd: 1, import: imp, } as SuiteConfig & SuiteResult;
|
|
70
|
+
const suite = asFull<SuiteConfig & SuiteResult>({ class: asFull<Class>({ name }), classId, duration: 0, lineStart: 1, lineEnd: 1, import: imp, });
|
|
73
71
|
err.message = err.message.replaceAll(Runtime.mainSourcePath, '.');
|
|
74
72
|
const res = AssertUtil.generateSuiteError(suite, 'require', err);
|
|
75
73
|
consumer.onEvent({ type: 'suite', phase: 'before', suite });
|
|
@@ -127,11 +125,11 @@ export class TestExecutor {
|
|
|
127
125
|
}
|
|
128
126
|
|
|
129
127
|
// Emit every assertion as it occurs
|
|
130
|
-
const getAssertions = AssertCapture.collector(test,
|
|
128
|
+
const getAssertions = AssertCapture.collector(test, asrt =>
|
|
131
129
|
consumer.onEvent({
|
|
132
130
|
type: 'assertion',
|
|
133
131
|
phase: 'after',
|
|
134
|
-
assertion:
|
|
132
|
+
assertion: asrt
|
|
135
133
|
})
|
|
136
134
|
);
|
|
137
135
|
|
package/src/execute/promise.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHook, executionAsyncId } from 'node:async_hooks';
|
|
2
|
+
import { isPromise } from 'node:util/types';
|
|
2
3
|
|
|
3
4
|
import { ExecutionError } from '@travetto/worker';
|
|
4
5
|
import { Util } from '@travetto/runtime';
|
|
@@ -11,9 +12,8 @@ export class PromiseCapturer {
|
|
|
11
12
|
#id: number = 0;
|
|
12
13
|
|
|
13
14
|
#init(id: number, type: string, triggerId: number, resource: unknown): void {
|
|
14
|
-
if (this.#id && type === 'PROMISE' && triggerId === this.#id) {
|
|
15
|
-
|
|
16
|
-
this.#pending.set(id, resource as Promise<unknown>);
|
|
15
|
+
if (this.#id && type === 'PROMISE' && triggerId === this.#id && isPromise(resource)) {
|
|
16
|
+
this.#pending.set(id, resource);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
package/src/registry/suite.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class,
|
|
1
|
+
import { Class, Runtime, classConstruct, describeFunction, asFull } from '@travetto/runtime';
|
|
2
2
|
import { MetadataRegistry } from '@travetto/registry';
|
|
3
3
|
|
|
4
4
|
import { SuiteConfig } from '../model/suite';
|
|
@@ -57,8 +57,7 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
57
57
|
* a full projection of all listeners and tests.
|
|
58
58
|
*/
|
|
59
59
|
onInstallFinalize<T>(cls: Class<T>): SuiteConfig {
|
|
60
|
-
|
|
61
|
-
const config = this.getOrCreatePending(cls) as SuiteConfig;
|
|
60
|
+
const config = asFull(this.getOrCreatePending(cls));
|
|
62
61
|
const tests = [...this.pendingFields.get(cls.Ⲑid)!.values()];
|
|
63
62
|
|
|
64
63
|
const parent = this.getParentClass(cls);
|
|
@@ -75,10 +74,8 @@ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> {
|
|
|
75
74
|
})));
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
config.
|
|
80
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
81
|
-
config.tests = tests as TestConfig[];
|
|
77
|
+
config.instance = classConstruct(config.class);
|
|
78
|
+
config.tests = tests!.map(x => asFull(x));
|
|
82
79
|
|
|
83
80
|
if (!config.description) {
|
|
84
81
|
config.description = config.classId;
|
|
@@ -137,12 +137,12 @@ export class AssertTransformer {
|
|
|
137
137
|
*/
|
|
138
138
|
static initState(state: TransformerState & AssertState): void {
|
|
139
139
|
if (!state[AssertⲐ]) {
|
|
140
|
-
const
|
|
140
|
+
const asrt = state.importFile('@travetto/test/src/assert/check').ident;
|
|
141
141
|
state[AssertⲐ] = {
|
|
142
|
-
assert:
|
|
143
|
-
assertCheck: CoreUtil.createAccess(state.factory,
|
|
144
|
-
checkThrow: CoreUtil.createAccess(state.factory,
|
|
145
|
-
checkThrowAsync: CoreUtil.createAccess(state.factory,
|
|
142
|
+
assert: asrt,
|
|
143
|
+
assertCheck: CoreUtil.createAccess(state.factory, asrt, ASSERT_UTIL, 'check'),
|
|
144
|
+
checkThrow: CoreUtil.createAccess(state.factory, asrt, ASSERT_UTIL, 'checkThrow'),
|
|
145
|
+
checkThrowAsync: CoreUtil.createAccess(state.factory, asrt, ASSERT_UTIL, 'checkThrowAsync'),
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
}
|
|
@@ -153,7 +153,7 @@ export class AssertTransformer {
|
|
|
153
153
|
static doAssert(state: TransformerState & AssertState, node: ts.CallExpression, cmd: Command): ts.CallExpression {
|
|
154
154
|
this.initState(state);
|
|
155
155
|
|
|
156
|
-
const first = CoreUtil.
|
|
156
|
+
const first = CoreUtil.firstArgument(node);
|
|
157
157
|
const firstText = first!.getText();
|
|
158
158
|
|
|
159
159
|
cmd.args = cmd.args.filter(x => x !== undefined && x !== null);
|
|
@@ -175,8 +175,8 @@ export class AssertTransformer {
|
|
|
175
175
|
* Convert `assert.(throws|rejects|doesNotThrow|doesNotReject)` to the appropriate structure
|
|
176
176
|
*/
|
|
177
177
|
static doThrows(state: TransformerState & AssertState, node: ts.CallExpression, key: string, args: ts.Expression[]): ts.CallExpression {
|
|
178
|
-
const first = CoreUtil.
|
|
179
|
-
const firstText = first
|
|
178
|
+
const first = CoreUtil.firstArgument(node)!;
|
|
179
|
+
const firstText = first.getText();
|
|
180
180
|
|
|
181
181
|
this.initState(state);
|
|
182
182
|
return state.factory.createCallExpression(
|