@travetto/test 8.0.0-alpha.8 → 8.0.0
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 +23 -21
- package/__index__.ts +9 -9
- package/package.json +20 -20
- package/src/assert/capture.ts +4 -5
- package/src/assert/check.ts +53 -26
- package/src/assert/types.ts +1 -1
- package/src/assert/util.ts +16 -12
- package/src/consumer/decorator.ts +2 -2
- package/src/consumer/enhancer.ts +3 -3
- package/src/consumer/registry-adapter.ts +3 -3
- package/src/consumer/registry-index.ts +6 -5
- package/src/consumer/types/cumulative.ts +6 -6
- package/src/consumer/types/delegating.ts +2 -2
- package/src/consumer/types/event.ts +2 -2
- package/src/consumer/types/exec.ts +3 -4
- package/src/consumer/types/json.ts +3 -4
- package/src/consumer/types/noop.ts +3 -3
- package/src/consumer/types/runnable.ts +4 -6
- package/src/consumer/types/summarizer.ts +2 -3
- package/src/consumer/types/tap-summary.ts +28 -25
- package/src/consumer/types/tap.ts +68 -40
- package/src/consumer/types/util.ts +8 -5
- package/src/consumer/types/xunit.ts +7 -6
- package/src/decorator/suite.ts +3 -3
- package/src/decorator/test.ts +6 -4
- package/src/execute/barrier.ts +15 -9
- package/src/execute/console.ts +3 -4
- package/src/execute/executor.ts +29 -25
- package/src/execute/phase.ts +1 -1
- package/src/execute/run.ts +38 -38
- package/src/execute/watcher.ts +12 -19
- package/src/fixture.ts +6 -7
- package/src/model/common.ts +1 -1
- package/src/model/error.ts +2 -2
- package/src/model/event.ts +14 -9
- package/src/model/suite.ts +2 -2
- package/src/model/test.ts +8 -5
- package/src/model/util.ts +25 -15
- package/src/registry/registry-adapter.ts +10 -13
- package/src/registry/registry-index.ts +13 -11
- package/src/trv.d.ts +2 -2
- package/src/worker/child.ts +13 -10
- package/src/worker/standard.ts +11 -14
- package/src/worker/types.ts +3 -7
- package/support/bin/run.ts +1 -1
- package/support/cli.test.ts +20 -15
- package/support/cli.test_child.ts +8 -3
- package/support/cli.test_diff.ts +11 -7
- package/support/cli.test_digest.ts +10 -4
- package/support/cli.test_direct.ts +11 -7
- package/support/cli.test_watch.ts +6 -4
- package/support/transformer.assert.ts +51 -43
package/README.md
CHANGED
|
@@ -18,10 +18,10 @@ This module provides unit testing functionality that integrates with the framewo
|
|
|
18
18
|
* [JSON](https://www.json.org), best for integrating with at a code level
|
|
19
19
|
* [xUnit](https://en.wikipedia.org/wiki/XUnit), standard format for CI/CD systems e.g. Jenkins, Bamboo, etc.
|
|
20
20
|
|
|
21
|
-
**Note**: All tests should be under the `**/*` folders.
|
|
21
|
+
**Note**: All tests should be under the `**/*` folders. The pattern for tests is defined as as a standard glob using [Node](https://nodejs.org)'s built in globbing support.
|
|
22
22
|
|
|
23
23
|
## Definition
|
|
24
|
-
A test suite is a collection of individual tests.
|
|
24
|
+
A test suite is a collection of individual tests. All test suites are classes with the [@Suite](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/suite.ts#L13) decorator. Tests are defined as methods on the suite class, using the [@Test](https://github.com/travetto/travetto/tree/main/module/test/src/decorator/test.ts#L25) decorator. All tests intrinsically support `async`/`await`.
|
|
25
25
|
|
|
26
26
|
A simple example would be:
|
|
27
27
|
|
|
@@ -33,7 +33,6 @@ import { Suite, Test } from '@travetto/test';
|
|
|
33
33
|
|
|
34
34
|
@Suite()
|
|
35
35
|
class SimpleTest {
|
|
36
|
-
|
|
37
36
|
#complexService: {
|
|
38
37
|
doLongOperation(): Promise<number>;
|
|
39
38
|
getText(): string;
|
|
@@ -54,7 +53,7 @@ class SimpleTest {
|
|
|
54
53
|
```
|
|
55
54
|
|
|
56
55
|
## Assertions
|
|
57
|
-
A common aspect of the tests themselves are the assertions that are made.
|
|
56
|
+
A common aspect of the tests themselves are the assertions that are made. [Node](https://nodejs.org) provides a built-in [assert](https://nodejs.org/api/assert.html) library. The framework uses AST transformations to modify the assertions to provide integration with the test module, and to provide a much higher level of detail in the failed assertions. For example:
|
|
58
57
|
|
|
59
58
|
**Code: Example assertion for deep comparison**
|
|
60
59
|
```typescript
|
|
@@ -64,7 +63,6 @@ import { Suite, Test } from '@travetto/test';
|
|
|
64
63
|
|
|
65
64
|
@Suite()
|
|
66
65
|
class SimpleTest {
|
|
67
|
-
|
|
68
66
|
@Test()
|
|
69
67
|
async test() {
|
|
70
68
|
assert.deepStrictEqual({ size: 20, address: { state: 'VA' } }, {});
|
|
@@ -86,11 +84,11 @@ const Δm_1 = ["@travetto/test", "doc/assert-example.ts"];
|
|
|
86
84
|
import assert from 'node:assert';
|
|
87
85
|
import { Suite, Test } from '@travetto/test';
|
|
88
86
|
let SimpleTest = class SimpleTest {
|
|
89
|
-
static { Δfunction.registerFunction(SimpleTest, Δm_1, { hash:
|
|
87
|
+
static { Δfunction.registerFunction(SimpleTest, Δm_1, { hash: 539050626, lines: [5, 11] }, { test: { hash: 102834457, lines: [7, 10, 9] } }, false); }
|
|
90
88
|
async test() {
|
|
91
89
|
if (Δdebug.tryDebugger)
|
|
92
90
|
debugger;
|
|
93
|
-
Δcheck.AssertCheck.check({ module: Δm_1, line:
|
|
91
|
+
Δcheck.AssertCheck.check({ module: Δm_1, line: 9, text: "{ size: 20, address: { state: 'VA' } }", operator: "deepStrictEqual" }, true, { size: 20, address: { state: 'VA' } }, {});
|
|
94
92
|
}
|
|
95
93
|
};
|
|
96
94
|
__decorate([
|
|
@@ -125,7 +123,7 @@ The equivalences for all of the [assert](https://nodejs.org/api/assert.html) ope
|
|
|
125
123
|
* `assert(a.includes(b))` as `assert.ok(a.includes(b))`
|
|
126
124
|
* `assert(/a/.test(b))` as `assert.ok(/a/.test(b))`
|
|
127
125
|
|
|
128
|
-
In addition to the standard operations, there is support for throwing/rejecting errors (or the inverse).
|
|
126
|
+
In addition to the standard operations, there is support for throwing/rejecting errors (or the inverse). This is useful for testing error states or ensuring errors do not occur.
|
|
129
127
|
|
|
130
128
|
### Throws
|
|
131
129
|
`throws`/`doesNotThrow` is for catching synchronous rejections
|
|
@@ -138,7 +136,6 @@ import { Suite, Test } from '@travetto/test';
|
|
|
138
136
|
|
|
139
137
|
@Suite()
|
|
140
138
|
class SimpleTest {
|
|
141
|
-
|
|
142
139
|
@Test()
|
|
143
140
|
async testThrows() {
|
|
144
141
|
assert.throws(() => {
|
|
@@ -146,7 +143,6 @@ class SimpleTest {
|
|
|
146
143
|
});
|
|
147
144
|
|
|
148
145
|
assert.doesNotThrow(() => {
|
|
149
|
-
|
|
150
146
|
let a = 5;
|
|
151
147
|
});
|
|
152
148
|
}
|
|
@@ -164,7 +160,6 @@ import { Suite, Test } from '@travetto/test';
|
|
|
164
160
|
|
|
165
161
|
@Suite()
|
|
166
162
|
class SimpleTest {
|
|
167
|
-
|
|
168
163
|
@Test()
|
|
169
164
|
async testRejects() {
|
|
170
165
|
await assert.rejects(async () => {
|
|
@@ -172,7 +167,6 @@ class SimpleTest {
|
|
|
172
167
|
});
|
|
173
168
|
|
|
174
169
|
await assert.doesNotReject(async () => {
|
|
175
|
-
|
|
176
170
|
let a = 5;
|
|
177
171
|
});
|
|
178
172
|
}
|
|
@@ -193,7 +187,6 @@ import { Suite, Test } from '@travetto/test';
|
|
|
193
187
|
|
|
194
188
|
@Suite()
|
|
195
189
|
class SimpleTest {
|
|
196
|
-
|
|
197
190
|
@Test()
|
|
198
191
|
async errorTypes() {
|
|
199
192
|
assert.throws(() => {
|
|
@@ -208,10 +201,11 @@ class SimpleTest {
|
|
|
208
201
|
throw new Error('Big Error');
|
|
209
202
|
}, Error);
|
|
210
203
|
|
|
211
|
-
await assert.rejects(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
204
|
+
await assert.rejects(
|
|
205
|
+
() => {
|
|
206
|
+
throw new Error('Big Error');
|
|
207
|
+
},
|
|
208
|
+
(error: Error) => error.message.startsWith('Big') && error.message.length > 4
|
|
215
209
|
);
|
|
216
210
|
}
|
|
217
211
|
}
|
|
@@ -220,12 +214,20 @@ class SimpleTest {
|
|
|
220
214
|
## Running Tests
|
|
221
215
|
To run the tests you can either call the [Command Line Interface](https://github.com/travetto/travetto/tree/main/module/cli#readme "CLI infrastructure for Travetto framework") by invoking
|
|
222
216
|
|
|
223
|
-
|
|
217
|
+
## CLI - test
|
|
218
|
+
|
|
219
|
+
**Terminal: Help for test**
|
|
224
220
|
```bash
|
|
225
221
|
$ trv test --help
|
|
226
222
|
|
|
227
223
|
Usage: test [options] [first:string] [globs...:string]
|
|
228
224
|
|
|
225
|
+
Description:
|
|
226
|
+
Execute the test framework for targeted files, suites, or methods.
|
|
227
|
+
|
|
228
|
+
Supports glob-based discovery, import-based targeting, tag filtering, and
|
|
229
|
+
configurable output consumers for local and CI workflows.
|
|
230
|
+
|
|
229
231
|
Options:
|
|
230
232
|
-f, --format <string> Output format for test results (default: "tap")
|
|
231
233
|
-c, --concurrency <number> Number of tests to run concurrently (default: 9)
|
|
@@ -237,9 +239,9 @@ Options:
|
|
|
237
239
|
The regexes are the patterns of tests you want to run, and all tests must be found under the `test/` folder.
|
|
238
240
|
|
|
239
241
|
### Travetto Plugin
|
|
240
|
-
The [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin) also supports test running,
|
|
242
|
+
The [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin) also supports test running, which will provide even more functionality for real-time testing and debugging.
|
|
241
243
|
|
|
242
244
|
## Additional Considerations
|
|
243
|
-
During the test execution, a few things additionally happen that should be helpful.
|
|
245
|
+
During the test execution, a few things additionally happen that should be helpful. The primary addition, is that all console output is captured, and will be exposed in the test output. This allows for investigation at a later point in time by analyzing the output.
|
|
244
246
|
|
|
245
|
-
Like output, all promises are also intercepted.
|
|
247
|
+
Like output, all promises are also intercepted. This allows the code to ensure that all promises have been resolved before completing the test. Any uncompleted promises will automatically trigger an error state and fail the test.
|
package/__index__.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {} from './src/trv.d.ts';
|
|
2
|
+
|
|
3
|
+
export * from './src/consumer/registry-index.ts';
|
|
4
|
+
export * from './src/consumer/types.ts';
|
|
2
5
|
export * from './src/decorator/suite.ts';
|
|
3
6
|
export * from './src/decorator/test.ts';
|
|
4
|
-
export * from './src/
|
|
7
|
+
export * from './src/fixture.ts';
|
|
5
8
|
export * from './src/model/error.ts';
|
|
6
|
-
export * from './src/model/test.ts';
|
|
7
9
|
export * from './src/model/event.ts';
|
|
10
|
+
export * from './src/model/suite.ts';
|
|
11
|
+
export * from './src/model/test.ts';
|
|
8
12
|
export * from './src/model/util.ts';
|
|
9
|
-
export * from './src/registry/registry-index.ts';
|
|
10
13
|
export * from './src/registry/registry-adapter.ts';
|
|
11
|
-
export * from './src/
|
|
12
|
-
export
|
|
13
|
-
export * from './src/consumer/registry-index.ts';
|
|
14
|
-
|
|
15
|
-
export { TestWatchEvent } from './src/worker/types.ts';
|
|
14
|
+
export * from './src/registry/registry-index.ts';
|
|
15
|
+
export { TestWatchEvent } from './src/worker/types.ts';
|
package/package.json
CHANGED
|
@@ -1,42 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/test",
|
|
3
|
-
"version": "8.0.0
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "8.0.0",
|
|
5
4
|
"description": "Declarative test framework",
|
|
6
5
|
"keywords": [
|
|
7
|
-
"
|
|
6
|
+
"ast-transformations",
|
|
7
|
+
"decorators",
|
|
8
8
|
"testing",
|
|
9
9
|
"travetto",
|
|
10
10
|
"typescript",
|
|
11
|
-
"
|
|
12
|
-
"ast-transformations"
|
|
11
|
+
"unit-testing"
|
|
13
12
|
],
|
|
14
13
|
"homepage": "https://travetto.io",
|
|
15
14
|
"license": "MIT",
|
|
16
15
|
"author": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"name": "Travetto Framework",
|
|
17
|
+
"email": "travetto.framework@gmail.com"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
21
|
+
"directory": "module/test"
|
|
19
22
|
},
|
|
20
23
|
"files": [
|
|
21
24
|
"__index__.ts",
|
|
22
25
|
"src",
|
|
23
26
|
"support"
|
|
24
27
|
],
|
|
28
|
+
"type": "module",
|
|
25
29
|
"main": "__index__.ts",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"directory": "module/test"
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
29
32
|
},
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"@travetto/registry": "^8.0.0
|
|
32
|
-
"@travetto/runtime": "^8.0.0
|
|
33
|
-
"@travetto/terminal": "^8.0.0
|
|
34
|
-
"@travetto/worker": "^8.0.0
|
|
35
|
-
"yaml": "^2.
|
|
34
|
+
"@travetto/registry": "^8.0.0",
|
|
35
|
+
"@travetto/runtime": "^8.0.0",
|
|
36
|
+
"@travetto/terminal": "^8.0.0",
|
|
37
|
+
"@travetto/worker": "^8.0.0",
|
|
38
|
+
"yaml": "^2.9.0"
|
|
36
39
|
},
|
|
37
40
|
"peerDependencies": {
|
|
38
|
-
"@travetto/cli": "^8.0.0
|
|
39
|
-
"@travetto/transformer": "^8.0.0
|
|
41
|
+
"@travetto/cli": "^8.0.0",
|
|
42
|
+
"@travetto/transformer": "^8.0.0"
|
|
40
43
|
},
|
|
41
44
|
"peerDependenciesMeta": {
|
|
42
45
|
"@travetto/transformer": {
|
|
@@ -52,8 +55,5 @@
|
|
|
52
55
|
"roles": [
|
|
53
56
|
"test"
|
|
54
57
|
]
|
|
55
|
-
},
|
|
56
|
-
"publishConfig": {
|
|
57
|
-
"access": "public"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/src/assert/capture.ts
CHANGED
|
@@ -14,7 +14,6 @@ export interface CapturedAssertion extends Partial<Assertion> {
|
|
|
14
14
|
* Assertion capturer
|
|
15
15
|
*/
|
|
16
16
|
class $AssertCapture {
|
|
17
|
-
|
|
18
17
|
#emitter = new EventEmitter();
|
|
19
18
|
|
|
20
19
|
/**
|
|
@@ -27,15 +26,15 @@ class $AssertCapture {
|
|
|
27
26
|
|
|
28
27
|
// Emit and collect, every assertion as it occurs
|
|
29
28
|
const handler = (a: CapturedAssertion): void => {
|
|
30
|
-
const
|
|
29
|
+
const assertion: Assertion = {
|
|
31
30
|
...a,
|
|
32
31
|
import: a.import ?? a.module!.join('/'),
|
|
33
32
|
classId: test.classId,
|
|
34
33
|
methodName: test.methodName
|
|
35
34
|
};
|
|
36
|
-
assertions.push(
|
|
35
|
+
assertions.push(assertion);
|
|
37
36
|
if (listener) {
|
|
38
|
-
listener(
|
|
37
|
+
listener(assertion);
|
|
39
38
|
}
|
|
40
39
|
};
|
|
41
40
|
|
|
@@ -52,4 +51,4 @@ class $AssertCapture {
|
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
export const AssertCapture = new $AssertCapture();
|
|
54
|
+
export const AssertCapture = new $AssertCapture();
|
package/src/assert/check.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { isPromise } from 'node:util/types';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { asConstructable, type Class, castKey, castTo, RuntimeError } from '@travetto/runtime';
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { TestExecutionError } from '../model/error.ts';
|
|
7
|
+
import type { Assertion, TestConfig, TestStatus, ThrowableError } from '../model/test.ts';
|
|
7
8
|
import { AssertCapture, type CapturedAssertion } from './capture.ts';
|
|
8
|
-
import { AssertUtil } from './util.ts';
|
|
9
9
|
import { ASSERT_FN_OPERATOR, OP_MAPPING } from './types.ts';
|
|
10
|
-
import {
|
|
10
|
+
import { AssertUtil } from './util.ts';
|
|
11
11
|
|
|
12
12
|
type StringFields<T> = {
|
|
13
|
-
[K in Extract<keyof T, string>]:
|
|
14
|
-
(T[K] extends string ? K : never)
|
|
13
|
+
[K in Extract<keyof T, string>]: T[K] extends string ? K : never;
|
|
15
14
|
}[Extract<keyof T, string>];
|
|
16
15
|
|
|
17
|
-
const isClass = (input: unknown): input is Class =>
|
|
16
|
+
const isClass = (input: unknown): input is Class =>
|
|
17
|
+
input === Error || input === RuntimeError || Object.getPrototypeOf(input) !== Object.getPrototypeOf(Function);
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Check assertion
|
|
@@ -36,7 +36,7 @@ export class AssertCheck {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
// Invert check for negative
|
|
39
|
-
const assertFn = positive ? assert : (value: unknown, msg?: string): unknown => assert(!value, msg);
|
|
39
|
+
const assertFn = positive ? assert : (value: unknown, msg?: string): unknown => assert(!value, msg!);
|
|
40
40
|
|
|
41
41
|
// Check fn to call
|
|
42
42
|
if (fn === 'fail') {
|
|
@@ -55,7 +55,7 @@ export class AssertCheck {
|
|
|
55
55
|
} else if (fn === 'ok' || fn === 'assert') {
|
|
56
56
|
fn = assertion.operator = 'ok';
|
|
57
57
|
[assertion.actual, assertion.message] = castTo(args);
|
|
58
|
-
assertion.expected = { toClean: (): string => positive ? 'truthy' : 'falsy' };
|
|
58
|
+
assertion.expected = { toClean: (): string => (positive ? 'truthy' : 'falsy') };
|
|
59
59
|
common.state = 'should be';
|
|
60
60
|
} else if (fn === 'includes') {
|
|
61
61
|
assertion.operator = fn;
|
|
@@ -64,7 +64,8 @@ export class AssertCheck {
|
|
|
64
64
|
assertion.operator = fn;
|
|
65
65
|
[assertion.actual, assertion.expected, assertion.message] = castTo(args);
|
|
66
66
|
assertion.actual = asConstructable(assertion.actual)?.constructor;
|
|
67
|
-
} else {
|
|
67
|
+
} else {
|
|
68
|
+
// Handle unknown
|
|
68
69
|
assertion.operator = fn ?? '';
|
|
69
70
|
[assertion.actual, assertion.expected, assertion.message] = castTo(args);
|
|
70
71
|
}
|
|
@@ -83,17 +84,36 @@ export class AssertCheck {
|
|
|
83
84
|
|
|
84
85
|
// Actually run the assertion
|
|
85
86
|
switch (fn) {
|
|
86
|
-
case 'includes':
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
case '
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
case '
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
case 'includes':
|
|
88
|
+
assertFn(castTo<unknown[]>(actual).includes(expected), message);
|
|
89
|
+
break;
|
|
90
|
+
case 'test':
|
|
91
|
+
assertFn(castTo<RegExp>(expected).test(castTo(actual)), message);
|
|
92
|
+
break;
|
|
93
|
+
case 'instanceof':
|
|
94
|
+
assertFn(actual instanceof castTo<Class>(expected), message);
|
|
95
|
+
break;
|
|
96
|
+
case 'in':
|
|
97
|
+
assertFn(castTo<string>(actual) in castTo<object>(expected), message);
|
|
98
|
+
break;
|
|
99
|
+
case 'lessThan':
|
|
100
|
+
assertFn(castTo<number>(actual) < castTo<number>(expected), message);
|
|
101
|
+
break;
|
|
102
|
+
case 'lessThanEqual':
|
|
103
|
+
assertFn(castTo<number>(actual) <= castTo<number>(expected), message);
|
|
104
|
+
break;
|
|
105
|
+
case 'greaterThan':
|
|
106
|
+
assertFn(castTo<number>(actual) > castTo<number>(expected), message);
|
|
107
|
+
break;
|
|
108
|
+
case 'greaterThanEqual':
|
|
109
|
+
assertFn(castTo<number>(actual) >= castTo<number>(expected), message);
|
|
110
|
+
break;
|
|
111
|
+
case 'ok':
|
|
112
|
+
assertFn.apply(null, castTo(args));
|
|
113
|
+
break;
|
|
95
114
|
default:
|
|
96
|
-
if (fn && assert[castKey<typeof assert>(fn)]) {
|
|
115
|
+
if (fn && assert[castKey<typeof assert>(fn)]) {
|
|
116
|
+
// Assert call
|
|
97
117
|
if (/not/i.test(fn)) {
|
|
98
118
|
common.state = 'should not';
|
|
99
119
|
}
|
|
@@ -107,7 +127,7 @@ export class AssertCheck {
|
|
|
107
127
|
// On error, produce the appropriate error message
|
|
108
128
|
if (error instanceof assert.AssertionError) {
|
|
109
129
|
if (!assertion.message) {
|
|
110
|
-
assertion.message =
|
|
130
|
+
assertion.message = OP_MAPPING[fn] ?? '{state} be {expected}';
|
|
111
131
|
}
|
|
112
132
|
assertion.message = assertion.message
|
|
113
133
|
.replace(/[{]([A-Za-z]+)[}]/g, (a, key: StringFields<Assertion>) => common[key] || assertion[key]!)
|
|
@@ -126,7 +146,8 @@ export class AssertCheck {
|
|
|
126
146
|
* @param error The provided error
|
|
127
147
|
*/
|
|
128
148
|
static checkError(shouldThrow: ThrowableError | undefined, error: Error | string | undefined): Error | undefined {
|
|
129
|
-
if (!shouldThrow) {
|
|
149
|
+
if (!shouldThrow) {
|
|
150
|
+
// If we shouldn't be throwing anything, we are good
|
|
130
151
|
return;
|
|
131
152
|
} else if (!error) {
|
|
132
153
|
return new assert.AssertionError({ message: 'Expected to throw an error, but got nothing' });
|
|
@@ -152,7 +173,7 @@ export class AssertCheck {
|
|
|
152
173
|
if (!(error instanceof shouldThrow)) {
|
|
153
174
|
return new assert.AssertionError({
|
|
154
175
|
message: `Expected to throw ${shouldThrow.name}, but got ${error}`,
|
|
155
|
-
actual:
|
|
176
|
+
actual: error ?? 'nothing',
|
|
156
177
|
expected: shouldThrow.name
|
|
157
178
|
});
|
|
158
179
|
}
|
|
@@ -220,7 +241,10 @@ export class AssertCheck {
|
|
|
220
241
|
if (typeof shouldThrow === 'function') {
|
|
221
242
|
shouldThrow = shouldThrow.name;
|
|
222
243
|
}
|
|
223
|
-
throw (missed = new assert.AssertionError({
|
|
244
|
+
throw (missed = new assert.AssertionError({
|
|
245
|
+
message: `No error thrown, but expected ${shouldThrow ?? 'an error'}`,
|
|
246
|
+
expected: shouldThrow ?? 'an error'
|
|
247
|
+
}));
|
|
224
248
|
}
|
|
225
249
|
} catch (error) {
|
|
226
250
|
this.#onError(positive, message, error, missed, shouldThrow, assertion);
|
|
@@ -256,7 +280,10 @@ export class AssertCheck {
|
|
|
256
280
|
if (typeof shouldThrow === 'function') {
|
|
257
281
|
shouldThrow = shouldThrow.name;
|
|
258
282
|
}
|
|
259
|
-
throw (missed = new assert.AssertionError({
|
|
283
|
+
throw (missed = new assert.AssertionError({
|
|
284
|
+
message: `No error thrown, but expected ${shouldThrow ?? 'an error'}`,
|
|
285
|
+
expected: shouldThrow ?? 'an error'
|
|
286
|
+
}));
|
|
260
287
|
}
|
|
261
288
|
} catch (error) {
|
|
262
289
|
this.#onError(positive, message, error, missed, shouldThrow, assertion);
|
|
@@ -299,4 +326,4 @@ export class AssertCheck {
|
|
|
299
326
|
return ['errored', error];
|
|
300
327
|
}
|
|
301
328
|
}
|
|
302
|
-
}
|
|
329
|
+
}
|
package/src/assert/types.ts
CHANGED
package/src/assert/util.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import util from 'node:util';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { hasFunction, JSONUtil, RuntimeIndex, Util } from '@travetto/runtime';
|
|
4
4
|
|
|
5
|
-
import type { Assertion, TestConfig } from '../model/test.ts';
|
|
6
5
|
import type { SuiteConfig } from '../model/suite.ts';
|
|
6
|
+
import type { Assertion, TestConfig } from '../model/test.ts';
|
|
7
7
|
|
|
8
8
|
const isCleanable = hasFunction<{ toClean(): unknown }>('toClean');
|
|
9
9
|
|
|
@@ -16,7 +16,12 @@ export class AssertUtil {
|
|
|
16
16
|
*/
|
|
17
17
|
static cleanValue(value: unknown): unknown {
|
|
18
18
|
switch (typeof value) {
|
|
19
|
-
case 'number':
|
|
19
|
+
case 'number':
|
|
20
|
+
case 'boolean':
|
|
21
|
+
case 'bigint':
|
|
22
|
+
case 'string':
|
|
23
|
+
case 'undefined':
|
|
24
|
+
return value;
|
|
20
25
|
case 'object': {
|
|
21
26
|
if (isCleanable(value)) {
|
|
22
27
|
return value.toClean();
|
|
@@ -38,12 +43,11 @@ export class AssertUtil {
|
|
|
38
43
|
/**
|
|
39
44
|
* Determine file location for a given error and the stack trace
|
|
40
45
|
*/
|
|
41
|
-
static getPositionOfError(error: Error): { import: string
|
|
42
|
-
const frames = Util.stackTraceToParts(error.stack ?? new Error().stack!)
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
46
|
+
static getPositionOfError(error: Error): { import: string; line: number } | undefined {
|
|
47
|
+
const frames = Util.stackTraceToParts(error.stack ?? new Error().stack!).map(frame => {
|
|
48
|
+
const entry = RuntimeIndex.getEntry(frame.filename);
|
|
49
|
+
return { ...frame, import: entry?.import ?? undefined!, line: entry?.type === 'ts' ? frame.line : 1 };
|
|
50
|
+
});
|
|
47
51
|
|
|
48
52
|
return frames.find(frame => frame.import);
|
|
49
53
|
}
|
|
@@ -51,9 +55,9 @@ export class AssertUtil {
|
|
|
51
55
|
/**
|
|
52
56
|
* Generate a suite error given a suite config, and an error
|
|
53
57
|
*/
|
|
54
|
-
static generateAssertion(config: { suite: SuiteConfig
|
|
58
|
+
static generateAssertion(config: { suite: SuiteConfig; test: TestConfig; error: Error; importLocation?: string }): Assertion {
|
|
55
59
|
const { suite, test, error: errorValue, importLocation } = config;
|
|
56
|
-
const error =
|
|
60
|
+
const error = errorValue.cause && errorValue.cause instanceof Error ? errorValue.cause : errorValue;
|
|
57
61
|
const testImport = importLocation ?? test.import;
|
|
58
62
|
const position = this.getPositionOfError(error);
|
|
59
63
|
const line = position?.line ?? (testImport === suite.import ? suite.lineStart : 1);
|
|
@@ -68,4 +72,4 @@ export class AssertUtil {
|
|
|
68
72
|
text: test.methodName
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
|
-
}
|
|
75
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Class } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
-
import type { TestConsumerShape } from './types.ts';
|
|
4
3
|
import { TestConsumerRegistryIndex } from './registry-index.ts';
|
|
4
|
+
import type { TestConsumerShape } from './types.ts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Registers a class a valid test consumer
|
|
@@ -10,4 +10,4 @@ export function TestConsumer(): (cls: Class<TestConsumerShape>) => void {
|
|
|
10
10
|
return function (cls: Class<TestConsumerShape>): void {
|
|
11
11
|
TestConsumerRegistryIndex.getForRegister(cls).register();
|
|
12
12
|
};
|
|
13
|
-
}
|
|
13
|
+
}
|
package/src/consumer/enhancer.ts
CHANGED
|
@@ -11,10 +11,10 @@ const input = {
|
|
|
11
11
|
assertLine: ['#ffffe0'], // light yellow
|
|
12
12
|
objectInspect: ['#cd00cd'], // Magenta
|
|
13
13
|
suiteName: ['#cdcd00'], // Yellow
|
|
14
|
-
testName: ['#00cdcd'],
|
|
15
|
-
total: ['#e5e5e5']
|
|
14
|
+
testName: ['#00cdcd'], // Cyan
|
|
15
|
+
total: ['#e5e5e5'] // White
|
|
16
16
|
} as const;
|
|
17
17
|
|
|
18
18
|
export const CONSOLE_ENHANCER = StyleUtil.getPalette(input);
|
|
19
19
|
|
|
20
|
-
export type TestResultsEnhancer = Record<keyof typeof input, TermStyleFn>;
|
|
20
|
+
export type TestResultsEnhancer = Record<keyof typeof input, TermStyleFn>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
-
import { classConstruct, describeFunction, type Class } from '@travetto/runtime';
|
|
4
3
|
import type { RegistryAdapter } from '@travetto/registry';
|
|
4
|
+
import { type Class, classConstruct, describeFunction } from '@travetto/runtime';
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { TestConsumerConfig, TestConsumerShape } from './types.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Test Results Handler Registry
|
|
@@ -39,4 +39,4 @@ export class TestConsumerRegistryAdapter implements RegistryAdapter<TestConsumer
|
|
|
39
39
|
await inst.setOptions?.(options);
|
|
40
40
|
return inst;
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { RuntimeIndex, type Class } from '@travetto/runtime';
|
|
2
1
|
import { Registry, type RegistryIndex, RegistryIndexStore } from '@travetto/registry';
|
|
2
|
+
import { type Class, RuntimeIndex } from '@travetto/runtime';
|
|
3
3
|
|
|
4
|
-
import type { TestConsumerShape } from './types.ts';
|
|
5
4
|
import type { TestConsumerConfig } from '../execute/types.ts';
|
|
6
5
|
import { TestConsumerRegistryAdapter } from './registry-adapter.ts';
|
|
6
|
+
import type { TestConsumerShape } from './types.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Test Results Handler Registry
|
|
10
10
|
*/
|
|
11
11
|
export class TestConsumerRegistryIndex implements RegistryIndex {
|
|
12
|
-
|
|
13
12
|
static #instance = Registry.registerIndex(this);
|
|
14
13
|
|
|
15
14
|
static getForRegister(cls: Class): TestConsumerRegistryAdapter {
|
|
@@ -34,7 +33,9 @@ export class TestConsumerRegistryIndex implements RegistryIndex {
|
|
|
34
33
|
#initialized: Promise<void>;
|
|
35
34
|
store = new RegistryIndexStore(TestConsumerRegistryAdapter);
|
|
36
35
|
|
|
37
|
-
/** @private */ constructor(source: unknown) {
|
|
36
|
+
/** @private */ constructor(source: unknown) {
|
|
37
|
+
Registry.validateConstructor(source);
|
|
38
|
+
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Manual initialization when running outside of the bootstrap process
|
|
@@ -79,4 +80,4 @@ export class TestConsumerRegistryIndex implements RegistryIndex {
|
|
|
79
80
|
}
|
|
80
81
|
throw new Error(`No test consumer registered for type ${state.consumer}`);
|
|
81
82
|
}
|
|
82
|
-
}
|
|
83
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SuiteCore } from '../../model/common.ts';
|
|
2
2
|
import type { TestEvent, TestRemoveEvent } from '../../model/event.ts';
|
|
3
|
-
import type { TestConfig, TestDiffSource, TestResult } from '../../model/test.ts';
|
|
4
3
|
import type { SuiteConfig, SuiteResult } from '../../model/suite.ts';
|
|
5
|
-
import {
|
|
6
|
-
import type { SuiteCore } from '../../model/common.ts';
|
|
4
|
+
import type { TestConfig, TestDiffSource, TestResult } from '../../model/test.ts';
|
|
7
5
|
import { TestModelUtil } from '../../model/util.ts';
|
|
6
|
+
import type { TestConsumerShape } from '../types.ts';
|
|
7
|
+
import { DelegatingConsumer } from './delegating.ts';
|
|
8
8
|
|
|
9
9
|
type ClassId = string;
|
|
10
10
|
type ImportName = string;
|
|
@@ -32,7 +32,7 @@ export class CumulativeSummaryConsumer extends DelegatingConsumer {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
getOrCreateSuite({ tests: _, ...core }: SuiteConfig | SuiteResult): CumulativeSuiteResult {
|
|
35
|
-
return (this.#state[core.import] ??= {})[core.classId] ??= { ...core, tests: {} };
|
|
35
|
+
return ((this.#state[core.import] ??= {})[core.classId] ??= { ...core, tests: {} });
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
onTestBefore(config: TestConfig): TestConfig {
|
|
@@ -114,4 +114,4 @@ export class CumulativeSummaryConsumer extends DelegatingConsumer {
|
|
|
114
114
|
}
|
|
115
115
|
return output;
|
|
116
116
|
}
|
|
117
|
-
}
|
|
117
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SuitesSummary, TestConsumerShape, TestRunState } from '../types.ts';
|
|
2
1
|
import type { TestEvent, TestRemoveEvent } from '../../model/event.ts';
|
|
2
|
+
import type { SuitesSummary, TestConsumerShape, TestRunState } from '../types.ts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Delegating event consumer
|
|
@@ -64,4 +64,4 @@ export abstract class DelegatingConsumer implements TestConsumerShape {
|
|
|
64
64
|
|
|
65
65
|
transform?(event: TestEvent): TestEvent | undefined;
|
|
66
66
|
transformRemove?(event: TestRemoveEvent): TestRemoveEvent | undefined;
|
|
67
|
-
}
|
|
67
|
+
}
|
|
@@ -3,8 +3,8 @@ import type { Writable } from 'node:stream';
|
|
|
3
3
|
import { JSONUtil } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import type { TestEvent, TestRemoveEvent } from '../../model/event.ts';
|
|
6
|
-
import type { TestConsumerShape } from '../types.ts';
|
|
7
6
|
import { TestConsumer } from '../decorator.ts';
|
|
7
|
+
import type { TestConsumerShape } from '../types.ts';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Streams all test events a JSON payload, in an nd-json format
|
|
@@ -28,4 +28,4 @@ export class EventStreamer implements TestConsumerShape {
|
|
|
28
28
|
onRemoveEvent(event: TestRemoveEvent): void {
|
|
29
29
|
this.sendPayload(event);
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|