@@ -12,6 +12,7 @@ const {
12
12
MathMax,
13
13
Number,
14
14
ObjectDefineProperty,
15
+ ObjectEntries,
15
16
ObjectSeal,
16
17
PromisePrototypeThen,
17
18
PromiseResolve,
@@ -88,6 +89,7 @@ const {
88
89
testOnlyFlag,
89
90
} = parseCommandLine ( ) ;
90
91
let kResistStopPropagation ;
92
+ let assertObj ;
91
93
let findSourceMap ;
92
94
let noopTestStream ;
93
95
@@ -101,6 +103,19 @@ function lazyFindSourceMap(file) {
101
103
return findSourceMap ( file ) ;
102
104
}
103
105
106
+ function lazyAssertObject ( ) {
107
+ if ( assertObj === undefined ) {
108
+ assertObj = new SafeMap ( ) ;
109
+ const assert = require ( 'assert' ) ;
110
+ for ( const { 0 : key , 1 : value } of ObjectEntries ( assert ) ) {
111
+ if ( typeof value === 'function' ) {
112
+ assertObj . set ( value , key ) ;
113
+ }
114
+ }
115
+ }
116
+ return assertObj ;
117
+ }
118
+
104
119
function stopTest ( timeout , signal ) {
105
120
const deferred = createDeferredPromise ( ) ;
106
121
const abortListener = addAbortListener ( signal , deferred . resolve ) ;
@@ -153,7 +168,25 @@ function testMatchesPattern(test, patterns) {
153
168
) ;
154
169
}
155
170
171
+ class TestPlan {
172
+ constructor ( count ) {
173
+ validateUint32 ( count , 'count' , 0 ) ;
174
+ this . expected = count ;
175
+ this . actual = 0 ;
176
+ }
177
+
178
+ check ( ) {
179
+ if ( this . actual !== this . expected ) {
180
+ throw new ERR_TEST_FAILURE (
181
+ `plan expected ${ this . expected } assertions but received ${ this . actual } ` ,
182
+ kTestCodeFailure ,
183
+ ) ;
184
+ }
185
+ }
186
+ }
187
+
156
188
class TestContext {
189
+ #assert;
157
190
#test;
158
191
159
192
constructor ( test ) {
@@ -180,6 +213,36 @@ class TestContext {
180
213
this . #test. diagnostic ( message ) ;
181
214
}
182
215
216
+ plan ( count ) {
217
+ if ( this . #test. plan !== null ) {
218
+ throw new ERR_TEST_FAILURE (
219
+ 'cannot set plan more than once' ,
220
+ kTestCodeFailure ,
221
+ ) ;
222
+ }
223
+
224
+ this . #test. plan = new TestPlan ( count ) ;
225
+ }
226
+
227
+ get assert ( ) {
228
+ if ( this . #assert === undefined ) {
229
+ const { plan } = this . #test;
230
+ const assertions = lazyAssertObject ( ) ;
231
+ const assert = { __proto__ : null } ;
232
+
233
+ this . #assert = assert ;
234
+ for ( const { 0 : method , 1 : name } of assertions . entries ( ) ) {
235
+ assert [ name ] = ( ...args ) => {
236
+ if ( plan !== null ) {
237
+ plan . actual ++ ;
238
+ }
239
+ return ReflectApply ( method , assert , args ) ;
240
+ } ;
241
+ }
242
+ }
243
+ return this . #assert;
244
+ }
245
+
183
246
get mock ( ) {
184
247
this . #test. mock ??= new MockTracker ( ) ;
185
248
return this . #test. mock ;
@@ -257,7 +320,7 @@ class Test extends AsyncResource {
257
320
super ( 'Test' ) ;
258
321
259
322
let { fn, name, parent } = options ;
260
- const { concurrency, loc, only, timeout, todo, skip, signal } = options ;
323
+ const { concurrency, loc, only, timeout, todo, skip, signal, plan } = options ;
261
324
262
325
if ( typeof fn !== 'function' ) {
263
326
fn = noop ;
@@ -373,6 +436,8 @@ class Test extends AsyncResource {
373
436
this . fn = fn ;
374
437
this . harness = null ; // Configured on the root test by the test harness.
375
438
this . mock = null ;
439
+ this . plan = null ;
440
+ this . expectedAssertions = plan ;
376
441
this . cancelled = false ;
377
442
this . skipped = skip !== undefined && skip !== false ;
378
443
this . isTodo = todo !== undefined && todo !== false ;
@@ -703,6 +768,11 @@ class Test extends AsyncResource {
703
768
704
769
const hookArgs = this . getRunArgs ( ) ;
705
770
const { args, ctx } = hookArgs ;
771
+
772
+ if ( this . plan === null && this . expectedAssertions ) {
773
+ ctx . plan ( this . expectedAssertions ) ;
774
+ }
775
+
706
776
const after = async ( ) => {
707
777
if ( this . hooks . after . length > 0 ) {
708
778
await this . runHook ( 'after' , hookArgs ) ;
@@ -754,7 +824,7 @@ class Test extends AsyncResource {
754
824
this . postRun ( ) ;
755
825
return ;
756
826
}
757
-
827
+ this . plan ?. check ( ) ;
758
828
this . pass ( ) ;
759
829
await afterEach ( ) ;
760
830
await after ( ) ;
@@ -910,7 +980,7 @@ class Test extends AsyncResource {
910
980
this . finished = true ;
911
981
912
982
if ( this . parent === this . root &&
913
- this . root . waitingOn > this . root . subtests . length ) {
983
+ this . root . waitingOn > this . root . subtests . length ) {
914
984
// At this point all of the tests have finished running. However, there
915
985
// might be ref'ed handles keeping the event loop alive. This gives the
916
986
// global after() hook a chance to clean them up. The user may also
@@ -1008,7 +1078,7 @@ class TestHook extends Test {
1008
1078
1009
1079
// Report failures in the root test's after() hook.
1010
1080
if ( error && parent !== null &&
1011
- parent === parent . root && this . hookType === 'after' ) {
1081
+ parent === parent . root && this . hookType === 'after' ) {
1012
1082
1013
1083
if ( isTestFailureError ( error ) ) {
1014
1084
error . failureType = kHookFailure ;
0 commit comments