vest 6.0.0 → 6.0.2
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/dist/exports/SuiteSerializer.cjs +45 -1
- package/dist/exports/SuiteSerializer.cjs.map +1 -1
- package/dist/exports/SuiteSerializer.mjs +45 -1
- package/dist/exports/SuiteSerializer.mjs.map +1 -1
- package/dist/exports/classnames.cjs +2 -2
- package/dist/exports/classnames.mjs +2 -2
- package/dist/exports/debounce.cjs +1 -1
- package/dist/exports/debounce.mjs +1 -1
- package/dist/exports/memo.cjs +1 -1
- package/dist/exports/memo.mjs +1 -1
- package/dist/exports/parser.cjs +2 -2
- package/dist/exports/parser.mjs +2 -2
- package/dist/{parser-CLhCAAYG.cjs → parser-DurNd9af.cjs} +2 -2
- package/dist/{parser-CLhCAAYG.cjs.map → parser-DurNd9af.cjs.map} +1 -1
- package/dist/{parser-Ch9qn6Hm.mjs → parser-TvzEAVLQ.mjs} +2 -2
- package/dist/{parser-Ch9qn6Hm.mjs.map → parser-TvzEAVLQ.mjs.map} +1 -1
- package/dist/{vest-CKYaivPf.cjs → vest-Lg6ZJuyu.cjs} +46 -12
- package/dist/vest-Lg6ZJuyu.cjs.map +1 -0
- package/dist/{vest-BEj6iXsK.mjs → vest-WSPRosdH.mjs} +47 -13
- package/dist/vest-WSPRosdH.mjs.map +1 -0
- package/dist/vest.cjs +1 -1
- package/dist/vest.mjs +1 -1
- package/package.json +6 -6
- package/src/core/Runtime.ts +3 -2
- package/src/exports/SuiteSerializer.ts +104 -1
- package/src/exports/__tests__/SuiteSerializer.test.ts +58 -1
- package/src/exports/__tests__/classnames.test.ts +24 -1
- package/src/exports/__tests__/parser.test.ts +23 -1
- package/src/suite/SuiteTypes.ts +15 -5
- package/src/suite/__tests__/runSummary.integration.test.ts +72 -0
- package/src/suite/__tests__/schema.types.test.ts +12 -0
- package/src/suite/useCreateSuiteRunner.ts +62 -41
- package/src/suiteResult/SuiteResultTypes.ts +22 -4
- package/src/suiteResult/selectors/useProduceSuiteSummary.ts +3 -2
- package/src/suiteResult/suiteResult.ts +37 -10
- package/types/{IsolateSuite-DnMtFHBW.d.cts → IsolateSuite-3gZF4eDG.d.mts} +11 -6
- package/types/IsolateSuite-3gZF4eDG.d.mts.map +1 -0
- package/types/{IsolateSuite-JdFYn2DA.d.mts → IsolateSuite-WsmXGGR-.d.cts} +11 -6
- package/types/IsolateSuite-WsmXGGR-.d.cts.map +1 -0
- package/types/{SuiteTypes-DESBCvZ7.d.cts → SuiteTypes-BNCL13HG.d.cts} +8 -7
- package/types/SuiteTypes-BNCL13HG.d.cts.map +1 -0
- package/types/{SuiteTypes-cjI5BA_k.d.mts → SuiteTypes-hg7F7swJ.d.mts} +8 -7
- package/types/SuiteTypes-hg7F7swJ.d.mts.map +1 -0
- package/types/exports/SuiteSerializer.d.cts +2 -2
- package/types/exports/SuiteSerializer.d.mts +2 -2
- package/types/exports/classnames.d.cts +2 -2
- package/types/exports/classnames.d.mts +2 -2
- package/types/exports/debounce.d.cts +1 -1
- package/types/exports/debounce.d.mts +1 -1
- package/types/exports/memo.d.cts +1 -1
- package/types/exports/memo.d.mts +1 -1
- package/types/exports/parser.d.cts +2 -2
- package/types/exports/parser.d.mts +2 -2
- package/types/{parser-BsrjuZ4_.d.cts → parser-DqW8l1IB.d.cts} +2 -2
- package/types/{parser-BsrjuZ4_.d.cts.map → parser-DqW8l1IB.d.cts.map} +1 -1
- package/types/{parser-CqvCfNJR.d.mts → parser-uCTQIcV6.d.mts} +2 -2
- package/types/{parser-CqvCfNJR.d.mts.map → parser-uCTQIcV6.d.mts.map} +1 -1
- package/types/vest.d.cts +2 -2
- package/types/vest.d.mts +2 -2
- package/types/vest.d.ts +2 -2
- package/dist/vest-BEj6iXsK.mjs.map +0 -1
- package/dist/vest-CKYaivPf.cjs.map +0 -1
- package/types/IsolateSuite-DnMtFHBW.d.cts.map +0 -1
- package/types/IsolateSuite-JdFYn2DA.d.mts.map +0 -1
- package/types/SuiteTypes-DESBCvZ7.d.cts.map +0 -1
- package/types/SuiteTypes-cjI5BA_k.d.mts.map +0 -1
|
@@ -16,7 +16,7 @@ type Dumpable = {
|
|
|
16
16
|
|
|
17
17
|
export class SuiteSerializer {
|
|
18
18
|
static serialize(suite: Dumpable) {
|
|
19
|
-
const dump =
|
|
19
|
+
const dump = stripMessageFromPassingTests(suite.dump());
|
|
20
20
|
|
|
21
21
|
return IsolateSerializer.serialize(dump, suiteSerializerReplacer);
|
|
22
22
|
}
|
|
@@ -50,6 +50,109 @@ export class SuiteSerializer {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function stripMessageFromPassingTests<T>(node: T): T {
|
|
54
|
+
const visited = new WeakMap<object, any>();
|
|
55
|
+
const containsPassingMemo = new WeakMap<object, boolean>();
|
|
56
|
+
const seen = new WeakSet<object>();
|
|
57
|
+
|
|
58
|
+
return strip(node, visited, containsPassingMemo, seen);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line complexity, max-statements
|
|
62
|
+
function strip<T>(
|
|
63
|
+
node: T,
|
|
64
|
+
visited: WeakMap<object, any>,
|
|
65
|
+
containsPassingMemo: WeakMap<object, boolean>,
|
|
66
|
+
seen: WeakSet<object>,
|
|
67
|
+
): T {
|
|
68
|
+
if (!node || typeof node !== 'object') {
|
|
69
|
+
return node;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (visited.has(node as object)) {
|
|
73
|
+
return visited.get(node as object);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!containsPassing(node, seen, containsPassingMemo)) {
|
|
77
|
+
return node;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (Array.isArray(node)) {
|
|
81
|
+
const arr: any[] = [];
|
|
82
|
+
visited.set(node, arr);
|
|
83
|
+
|
|
84
|
+
node.forEach(value => {
|
|
85
|
+
arr.push(strip(value, visited, containsPassingMemo, seen));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return arr as T;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const root = node as Record<string, any>;
|
|
92
|
+
const shouldStripMessage = root.testStatus === TestStatus.PASSING;
|
|
93
|
+
const clonedNode: Record<string, any> = {};
|
|
94
|
+
visited.set(node, clonedNode);
|
|
95
|
+
|
|
96
|
+
for (const [key, value] of Object.entries(root)) {
|
|
97
|
+
if (shouldStripMessage && key === 'message') {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
clonedNode[key] = strip(value, visited, containsPassingMemo, seen);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return clonedNode as T;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// `strip` calls this with a shared `seen` set that is expected to be empty
|
|
108
|
+
// at call boundaries. Inside this DFS, revisiting a node (`seen.has(node)`) means
|
|
109
|
+
// we've encountered a cycle currently in-flight, so we conservatively return `true`
|
|
110
|
+
// to avoid suppressing message-stripping while still preventing infinite recursion.
|
|
111
|
+
// This is safe because every path that adds to `seen` removes it (`seen.delete`) on
|
|
112
|
+
// return, and `memo` only stores final computed booleans for completed nodes.
|
|
113
|
+
// eslint-disable-next-line complexity, max-statements
|
|
114
|
+
function containsPassing(
|
|
115
|
+
node: unknown,
|
|
116
|
+
seen: WeakSet<object>,
|
|
117
|
+
memo: WeakMap<object, boolean>,
|
|
118
|
+
): boolean {
|
|
119
|
+
if (!node || typeof node !== 'object') {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (memo.has(node)) {
|
|
124
|
+
return memo.get(node) ?? false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (seen.has(node)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if ((node as Record<string, unknown>).testStatus === TestStatus.PASSING) {
|
|
132
|
+
memo.set(node, true);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
seen.add(node);
|
|
137
|
+
|
|
138
|
+
const values = Array.isArray(node)
|
|
139
|
+
? node
|
|
140
|
+
: Object.values(node as Record<string, unknown>);
|
|
141
|
+
|
|
142
|
+
for (const value of values) {
|
|
143
|
+
if (containsPassing(value, seen, memo)) {
|
|
144
|
+
memo.set(node, true);
|
|
145
|
+
seen.delete(node);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
memo.set(node, false);
|
|
151
|
+
seen.delete(node);
|
|
152
|
+
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
53
156
|
function suiteSerializerReplacer(value: any, key: string) {
|
|
54
157
|
if (isStatusKey(key)) {
|
|
55
158
|
return getAllowedStatus(value);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, expectTypeOf } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { SuiteSerializer } from '../SuiteSerializer';
|
|
4
4
|
import * as vest from '../../vest';
|
|
@@ -26,6 +26,35 @@ describe('SuiteSerializer', () => {
|
|
|
26
26
|
const serialized = SuiteSerializer.serialize(suite);
|
|
27
27
|
expect(serialized).toMatchSnapshot();
|
|
28
28
|
});
|
|
29
|
+
|
|
30
|
+
it('should strip message from passing tests only', () => {
|
|
31
|
+
const suite = vest.create(() => {
|
|
32
|
+
vest.test('passing_field', 'passing_field_message', () => true);
|
|
33
|
+
vest.test('failing_field', 'failing_field_message', () => false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
suite.run();
|
|
37
|
+
|
|
38
|
+
const serialized = SuiteSerializer.serialize(suite);
|
|
39
|
+
const parsed = SuiteSerializer.deserialize(serialized);
|
|
40
|
+
|
|
41
|
+
expect(parsed.children).toBeDefined();
|
|
42
|
+
|
|
43
|
+
const testChildren = parsed.children?.filter(child => child.data.fieldName);
|
|
44
|
+
expect(testChildren).toHaveLength(2);
|
|
45
|
+
|
|
46
|
+
const passingTest = testChildren?.find(
|
|
47
|
+
child => child.data.fieldName === 'passing_field',
|
|
48
|
+
);
|
|
49
|
+
const failingTest = testChildren?.find(
|
|
50
|
+
child => child.data.fieldName === 'failing_field',
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(passingTest).toBeDefined();
|
|
54
|
+
expect(failingTest).toBeDefined();
|
|
55
|
+
expect(passingTest?.data).not.toHaveProperty('message');
|
|
56
|
+
expect(failingTest?.data.message).toBe('failing_field_message');
|
|
57
|
+
});
|
|
29
58
|
});
|
|
30
59
|
|
|
31
60
|
describe('suite.resume', () => {
|
|
@@ -117,3 +146,31 @@ describe('suite.resume', () => {
|
|
|
117
146
|
});
|
|
118
147
|
});
|
|
119
148
|
});
|
|
149
|
+
|
|
150
|
+
describe('SuiteSerializer type-compatibility', () => {
|
|
151
|
+
it('accepts schema-typed suites in resume()', () => {
|
|
152
|
+
const schema = vest.enforce.shape({
|
|
153
|
+
username: vest.enforce.isString(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const suite = vest.create(data => {
|
|
157
|
+
vest.test('username', () => {
|
|
158
|
+
vest.enforce(data.username).isNotBlank();
|
|
159
|
+
});
|
|
160
|
+
}, schema);
|
|
161
|
+
|
|
162
|
+
suite.run({ username: '' });
|
|
163
|
+
|
|
164
|
+
const serialized = SuiteSerializer.serialize(suite);
|
|
165
|
+
|
|
166
|
+
const suite2 = vest.create(data => {
|
|
167
|
+
vest.test('username', () => {
|
|
168
|
+
vest.enforce(data.username).isNotBlank();
|
|
169
|
+
});
|
|
170
|
+
}, schema);
|
|
171
|
+
|
|
172
|
+
expectTypeOf(SuiteSerializer.resume).toBeFunction();
|
|
173
|
+
SuiteSerializer.resume(suite2, serialized);
|
|
174
|
+
expect(suite2.hasErrors('username')).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
1
|
+
import { describe, it, expect, expectTypeOf, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { dummyTest } from '../../testUtils/testDummy';
|
|
4
4
|
import classnames from '../classnames';
|
|
@@ -60,6 +60,29 @@ describe('Utility: classnames', () => {
|
|
|
60
60
|
expect(cn('travelerName')).toContain('invalid');
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
it('accepts schema-typed suite results from suite.run() and preserves parser output type', () => {
|
|
64
|
+
const typedSuite = vest.create(
|
|
65
|
+
data => {
|
|
66
|
+
vest.test('travelerName', 'Traveler name is required', () => {
|
|
67
|
+
vest.enforce(data.travelerName).isNotBlank();
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
vest.enforce.shape({
|
|
71
|
+
travelerName: vest.enforce.isString(),
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const result = typedSuite.run({ travelerName: '' });
|
|
76
|
+
|
|
77
|
+
const cn = classnames(result, {
|
|
78
|
+
warning: 'warning',
|
|
79
|
+
invalid: 'invalid',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expectTypeOf(cn).toEqualTypeOf<(fieldName: string) => string>();
|
|
83
|
+
expect(cn('travelerName')).toContain('invalid');
|
|
84
|
+
});
|
|
85
|
+
|
|
63
86
|
const suite = vest.create(() => {
|
|
64
87
|
vest.mode(Modes.ALL);
|
|
65
88
|
vest.skip('field_1');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, expectTypeOf } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import * as suiteDummy from '../../testUtils/suiteDummy';
|
|
4
4
|
import { ser } from '../../testUtils/suiteDummy';
|
|
@@ -501,3 +501,25 @@ describe('parser.parse', () => {
|
|
|
501
501
|
});
|
|
502
502
|
});
|
|
503
503
|
});
|
|
504
|
+
|
|
505
|
+
describe('parser.parse type-compatibility', () => {
|
|
506
|
+
it('accepts schema-driven suite.run() results and returns typed selectors', () => {
|
|
507
|
+
const suite = vest.create(
|
|
508
|
+
data => {
|
|
509
|
+
vest.test('email', () => {
|
|
510
|
+
vest.enforce(data.email).isString();
|
|
511
|
+
});
|
|
512
|
+
},
|
|
513
|
+
vest.enforce.shape({
|
|
514
|
+
email: vest.enforce.isString(),
|
|
515
|
+
}),
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
const parsed = parse(suite.run({ email: 'a@b.com' }));
|
|
519
|
+
|
|
520
|
+
expectTypeOf(parsed.valid).toEqualTypeOf<
|
|
521
|
+
(fieldName?: string | undefined) => boolean
|
|
522
|
+
>();
|
|
523
|
+
expect(parsed.valid('email')).toBe(true);
|
|
524
|
+
});
|
|
525
|
+
});
|
package/src/suite/SuiteTypes.ts
CHANGED
|
@@ -15,6 +15,16 @@ import { SuiteSelectors } from '../suiteResult/selectors/suiteSelectors';
|
|
|
15
15
|
|
|
16
16
|
import { TTypedMethods } from './getTypedMethods';
|
|
17
17
|
|
|
18
|
+
export type SuiteRunData<
|
|
19
|
+
S extends TSchema,
|
|
20
|
+
T extends CB,
|
|
21
|
+
IsFocused extends boolean = false,
|
|
22
|
+
> = S extends undefined
|
|
23
|
+
? Parameters<T>[0]
|
|
24
|
+
: IsFocused extends true
|
|
25
|
+
? Partial<InferSchemaData<S>>
|
|
26
|
+
: InferSchemaData<S>;
|
|
27
|
+
|
|
18
28
|
export type SuiteCallbackWithSchema<
|
|
19
29
|
S extends TSchema,
|
|
20
30
|
T extends CB,
|
|
@@ -47,17 +57,17 @@ type SuiteMethods<
|
|
|
47
57
|
...args: S extends undefined
|
|
48
58
|
? Parameters<T>
|
|
49
59
|
: [data: InferSchemaData<S>, ...args: any[]]
|
|
50
|
-
) => SuiteResult<F, G
|
|
60
|
+
) => SuiteResult<F, G, S, SuiteRunData<S, T>>;
|
|
51
61
|
runStatic: (
|
|
52
62
|
...args: S extends undefined
|
|
53
63
|
? Parameters<T>
|
|
54
64
|
: [data: InferSchemaData<S>, ...args: any[]]
|
|
55
|
-
) => SuiteResult<F, G
|
|
65
|
+
) => SuiteResult<F, G, S, SuiteRunData<S, T>>;
|
|
56
66
|
validate: (
|
|
57
67
|
...args: S extends undefined
|
|
58
68
|
? Parameters<T>
|
|
59
69
|
: [data: InferSchemaData<S>, ...args: any[]]
|
|
60
|
-
) => SuiteResult<F, G
|
|
70
|
+
) => SuiteResult<F, G, S, SuiteRunData<S, T>>;
|
|
61
71
|
subscribe: Subscribe;
|
|
62
72
|
} & AfterMethods<F, G, T, S> &
|
|
63
73
|
TTypedMethods<F, G> &
|
|
@@ -85,7 +95,7 @@ type FocusedMethods<
|
|
|
85
95
|
...args: S extends undefined
|
|
86
96
|
? Parameters<T>
|
|
87
97
|
: [data: Partial<InferSchemaData<S>>, ...args: any[]]
|
|
88
|
-
) => SuiteResult<F, G
|
|
98
|
+
) => SuiteResult<F, G, S, SuiteRunData<S, T, true>>;
|
|
89
99
|
};
|
|
90
100
|
|
|
91
101
|
type AfterMethods<
|
|
@@ -108,7 +118,7 @@ type AfterMethods<
|
|
|
108
118
|
...args: S extends undefined
|
|
109
119
|
? Parameters<T>
|
|
110
120
|
: [data: InferSchemaData<S>, ...args: any[]]
|
|
111
|
-
) => SuiteResult<F, G
|
|
121
|
+
) => SuiteResult<F, G, S, SuiteRunData<S, T>>;
|
|
112
122
|
};
|
|
113
123
|
|
|
114
124
|
/**
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { create } from '../../vest';
|
|
4
|
+
|
|
5
|
+
describe('suite result run summary metadata', () => {
|
|
6
|
+
it('stores the original run object and timestamp for non-schema suites', () => {
|
|
7
|
+
const payload = { name: 'alice', meta: { age: 33 } };
|
|
8
|
+
const suite = create((_data: any) => {});
|
|
9
|
+
|
|
10
|
+
const before = Date.now();
|
|
11
|
+
const result = suite.run(payload);
|
|
12
|
+
const after = Date.now();
|
|
13
|
+
|
|
14
|
+
expect(result.run.data).toBe(payload);
|
|
15
|
+
expect(Object.prototype.propertyIsEnumerable.call(result, 'run')).toBe(
|
|
16
|
+
false,
|
|
17
|
+
);
|
|
18
|
+
expect(Object.isFrozen(result.run)).toBe(true);
|
|
19
|
+
expect(result.run.time).toBeInstanceOf(Date);
|
|
20
|
+
expect(result.run.time.getTime()).toBeGreaterThanOrEqual(before);
|
|
21
|
+
expect(result.run.time.getTime()).toBeLessThanOrEqual(after);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('stores parsed schema data in run metadata', () => {
|
|
25
|
+
const schema = {
|
|
26
|
+
parse: (value: any) => ({ amount: Number(value.amount) }),
|
|
27
|
+
run: (value: any) => ({ pass: true, type: value }),
|
|
28
|
+
} as any;
|
|
29
|
+
|
|
30
|
+
let callbackValue: unknown;
|
|
31
|
+
const suite = create((data: any) => {
|
|
32
|
+
callbackValue = data;
|
|
33
|
+
}, schema);
|
|
34
|
+
|
|
35
|
+
const result = suite.run({ amount: '12' } as any);
|
|
36
|
+
|
|
37
|
+
expect(result.run.data).toEqual({ amount: 12 });
|
|
38
|
+
expect(callbackValue).toEqual(result.run.data);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('falls back to the original data in run metadata when parse fails', () => {
|
|
42
|
+
const schema = {
|
|
43
|
+
parse: () => {
|
|
44
|
+
throw new TypeError('parse failed');
|
|
45
|
+
},
|
|
46
|
+
run: (value: any) => ({
|
|
47
|
+
pass: true,
|
|
48
|
+
type: value,
|
|
49
|
+
}),
|
|
50
|
+
} as any;
|
|
51
|
+
|
|
52
|
+
const payload = { amount: '12' };
|
|
53
|
+
const suite = create(() => {}, schema);
|
|
54
|
+
|
|
55
|
+
const result = suite.run(payload);
|
|
56
|
+
|
|
57
|
+
expect(result.run.data).toBe(payload);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('captures run.time per run', () => {
|
|
61
|
+
const suite = create((_data: any) => {});
|
|
62
|
+
|
|
63
|
+
const first = suite.run({ count: 1 });
|
|
64
|
+
const second = suite.run({ count: 2 });
|
|
65
|
+
|
|
66
|
+
expect(second.run.time.getTime()).toBeGreaterThanOrEqual(
|
|
67
|
+
first.run.time.getTime(),
|
|
68
|
+
);
|
|
69
|
+
expect(first.run.data).toEqual({ count: 1 });
|
|
70
|
+
expect(second.run.data).toEqual({ count: 2 });
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -33,6 +33,10 @@ describe('schema driven suite types', () => {
|
|
|
33
33
|
|
|
34
34
|
suite.run({ name: 'john', age: 42 });
|
|
35
35
|
|
|
36
|
+
expectTypeOf(suite.run({ name: 'jane', age: 30 }).run.data).toEqualTypeOf<
|
|
37
|
+
{ name: string; age: number } | undefined
|
|
38
|
+
>();
|
|
39
|
+
|
|
36
40
|
void (0 as unknown as AssertTrue<
|
|
37
41
|
IsEqual<
|
|
38
42
|
ReturnType<typeof suite.get>['types']['output'],
|
|
@@ -140,6 +144,10 @@ describe('schema driven suite types', () => {
|
|
|
140
144
|
|
|
141
145
|
suite.run(10, true);
|
|
142
146
|
|
|
147
|
+
expectTypeOf(suite.run(10, true).run.data).toEqualTypeOf<
|
|
148
|
+
number | undefined
|
|
149
|
+
>();
|
|
150
|
+
|
|
143
151
|
void (0 as unknown as AssertTrue<
|
|
144
152
|
IsEqual<ReturnType<typeof suite.get>['types'], undefined>
|
|
145
153
|
>);
|
|
@@ -158,6 +166,10 @@ describe('schema driven suite types', () => {
|
|
|
158
166
|
|
|
159
167
|
suite.run({ name: 'john', age: 42 });
|
|
160
168
|
|
|
169
|
+
expectTypeOf(suite.run({ name: 'john', age: 42 }).run.data).toEqualTypeOf<
|
|
170
|
+
{ name: string; age: number } | undefined
|
|
171
|
+
>();
|
|
172
|
+
|
|
161
173
|
expectTypeOf(suite.get().types).toBeUndefined();
|
|
162
174
|
|
|
163
175
|
// @ts-expect-error - requires an argument
|
|
@@ -24,7 +24,11 @@ import {
|
|
|
24
24
|
} from '../suiteResult/SuiteResultTypes';
|
|
25
25
|
import { useCreateSuiteResult } from '../suiteResult/suiteResult';
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
SuiteModifiers,
|
|
29
|
+
SuiteCallbackWithSchema,
|
|
30
|
+
SuiteRunData,
|
|
31
|
+
} from './SuiteTypes';
|
|
28
32
|
|
|
29
33
|
type SchemaRunResult = {
|
|
30
34
|
readonly message?: string;
|
|
@@ -56,8 +60,10 @@ export function useCreateSuiteRunner<
|
|
|
56
60
|
...args: S extends undefined
|
|
57
61
|
? Parameters<T>
|
|
58
62
|
: [data: InferSchemaData<S>, ...args: any[]]
|
|
59
|
-
): SuiteResult<F, G, S
|
|
60
|
-
const
|
|
63
|
+
): SuiteResult<F, G, S, SuiteRunData<S, T>> {
|
|
64
|
+
const runTime = new Date();
|
|
65
|
+
const { resolve, promise } =
|
|
66
|
+
withResolvers<SuiteResult<F, G, S, SuiteRunData<S, T>>>();
|
|
61
67
|
|
|
62
68
|
const schemaInput = args[0];
|
|
63
69
|
const schemaRunResult = shouldRunSchema(schema, transformedModifiers)
|
|
@@ -66,46 +72,59 @@ export function useCreateSuiteRunner<
|
|
|
66
72
|
|
|
67
73
|
const callbackInput = getCallbackInput(schemaRunResult, schemaInput);
|
|
68
74
|
const callbackArgs = [callbackInput, ...args.slice(1)] as Parameters<T>;
|
|
75
|
+
const runData = callbackInput as SuiteRunData<S, T> | undefined;
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
suiteCallback,
|
|
102
|
-
useResolver,
|
|
103
|
-
}),
|
|
77
|
+
const suiteResult = SuiteContext.run(
|
|
78
|
+
{
|
|
79
|
+
suiteParams: callbackArgs,
|
|
80
|
+
schema,
|
|
81
|
+
modifiers: transformedModifiers,
|
|
82
|
+
},
|
|
83
|
+
() => {
|
|
84
|
+
useEmit('SUITE_RUN_STARTED');
|
|
85
|
+
|
|
86
|
+
const useResolver = () => {
|
|
87
|
+
const result = useCreateSuiteResult<F, G, S, SuiteRunData<S, T>>(
|
|
88
|
+
schema,
|
|
89
|
+
callbackInput,
|
|
90
|
+
runData,
|
|
91
|
+
runTime,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (!result.isPending()) {
|
|
95
|
+
resolve(result);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return IsolateSuite(
|
|
102
|
+
useRunSuiteCallback<F, T, S, G, SuiteRunData<S, T>>({
|
|
103
|
+
args: callbackArgs,
|
|
104
|
+
modifiers: transformedModifiers,
|
|
105
|
+
schema,
|
|
106
|
+
schemaRunResult,
|
|
107
|
+
suiteCallback,
|
|
104
108
|
useResolver,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
}),
|
|
110
|
+
useResolver,
|
|
111
|
+
).output;
|
|
112
|
+
},
|
|
108
113
|
);
|
|
114
|
+
|
|
115
|
+
const result = assign(promise, suiteResult);
|
|
116
|
+
|
|
117
|
+
Object.defineProperty(result, 'run', {
|
|
118
|
+
configurable: true,
|
|
119
|
+
enumerable: false,
|
|
120
|
+
value: Object.freeze({
|
|
121
|
+
data: runData,
|
|
122
|
+
time: runTime,
|
|
123
|
+
}),
|
|
124
|
+
writable: true,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return result;
|
|
109
128
|
};
|
|
110
129
|
}
|
|
111
130
|
|
|
@@ -131,13 +150,15 @@ function useRunSuiteCallback<
|
|
|
131
150
|
F extends TFieldName,
|
|
132
151
|
T extends CB = CB,
|
|
133
152
|
S extends TSchema = undefined,
|
|
153
|
+
G extends TGroupName = TGroupName,
|
|
154
|
+
D = unknown,
|
|
134
155
|
>(params: {
|
|
135
156
|
args: any[];
|
|
136
157
|
modifiers: ReturnType<typeof useTransformedModifiers<F>>;
|
|
137
158
|
schema: S | undefined;
|
|
138
159
|
schemaRunResult?: SchemaRunResult[];
|
|
139
160
|
suiteCallback: SuiteCallbackWithSchema<S, T>;
|
|
140
|
-
useResolver: () => SuiteResult<F,
|
|
161
|
+
useResolver: () => SuiteResult<F, G, S, D>;
|
|
141
162
|
}) {
|
|
142
163
|
const {
|
|
143
164
|
args,
|
|
@@ -17,12 +17,28 @@ export class SummaryBase {
|
|
|
17
17
|
export class SuiteSummary<
|
|
18
18
|
F extends TFieldName,
|
|
19
19
|
G extends TGroupName,
|
|
20
|
+
D = unknown,
|
|
20
21
|
> extends SummaryBase {
|
|
21
22
|
public [Severity.ERRORS]: SummaryFailure<F, G>[] = [];
|
|
22
23
|
public [Severity.WARNINGS]: SummaryFailure<F, G>[] = [];
|
|
23
24
|
public groups: Groups<G, F> = {} as Groups<G, F>;
|
|
24
25
|
public tests: Tests<F> = {} as Tests<F>;
|
|
26
|
+
public run!: { data: D | undefined; time: Date };
|
|
25
27
|
public valid: Nullable<boolean> = null;
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
|
|
32
|
+
Object.defineProperty(this, 'run', {
|
|
33
|
+
configurable: true,
|
|
34
|
+
enumerable: false,
|
|
35
|
+
value: {
|
|
36
|
+
data: undefined,
|
|
37
|
+
time: new Date(0),
|
|
38
|
+
},
|
|
39
|
+
writable: true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
26
42
|
}
|
|
27
43
|
|
|
28
44
|
export type TestsContainer<F extends TFieldName, _G extends TGroupName> =
|
|
@@ -75,20 +91,21 @@ type SuiteResultData<
|
|
|
75
91
|
F extends TFieldName,
|
|
76
92
|
G extends TGroupName,
|
|
77
93
|
S extends TSchema = undefined,
|
|
94
|
+
D = unknown,
|
|
78
95
|
> =
|
|
79
|
-
| (Omit<SuiteSummary<F, G>, 'valid'> &
|
|
96
|
+
| (Omit<SuiteSummary<F, G, D>, 'valid'> &
|
|
80
97
|
SuiteSelectors<F, G> & {
|
|
81
98
|
valid: true;
|
|
82
99
|
value: InferSchemaOutput<S>;
|
|
83
100
|
issues?: undefined;
|
|
84
101
|
})
|
|
85
|
-
| (Omit<SuiteSummary<F, G>, 'valid'> &
|
|
102
|
+
| (Omit<SuiteSummary<F, G, D>, 'valid'> &
|
|
86
103
|
SuiteSelectors<F, G> & {
|
|
87
104
|
valid: false;
|
|
88
105
|
issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
89
106
|
value?: undefined;
|
|
90
107
|
})
|
|
91
|
-
| (Omit<SuiteSummary<F, G>, 'valid'> &
|
|
108
|
+
| (Omit<SuiteSummary<F, G, D>, 'valid'> &
|
|
92
109
|
SuiteSelectors<F, G> & {
|
|
93
110
|
valid: null;
|
|
94
111
|
issues?: undefined;
|
|
@@ -102,7 +119,8 @@ export type SuiteResult<
|
|
|
102
119
|
F extends string = TFieldName,
|
|
103
120
|
G extends string = TGroupName,
|
|
104
121
|
S extends TSchema = undefined,
|
|
105
|
-
|
|
122
|
+
D = unknown,
|
|
123
|
+
> = SuiteResultData<BrandedFieldName<F>, BrandedGroupName<G>, S, D> & {
|
|
106
124
|
dump: CB<TIsolateSuite>;
|
|
107
125
|
types: S extends undefined
|
|
108
126
|
? undefined
|
|
@@ -26,10 +26,11 @@ import {
|
|
|
26
26
|
export function useProduceSuiteSummary<
|
|
27
27
|
F extends TFieldName,
|
|
28
28
|
G extends TGroupName,
|
|
29
|
-
|
|
29
|
+
D = unknown,
|
|
30
|
+
>(): SuiteSummary<F, G, D> {
|
|
30
31
|
const root = VestRuntime.useAvailableRoot<TIsolateSuite>();
|
|
31
32
|
|
|
32
|
-
const summary = new SuiteSummary<F, G>();
|
|
33
|
+
const summary = new SuiteSummary<F, G, D>();
|
|
33
34
|
|
|
34
35
|
if (isVestIsolate(root)) {
|
|
35
36
|
useProcessTests(root.data.tests, summary);
|