@@ -11,6 +11,7 @@ const {
1111 FunctionPrototype,
1212 MathMax,
1313 Number,
14+ ObjectEntries,
1415 ObjectSeal,
1516 PromisePrototypeThen,
1617 PromiseResolve,
@@ -85,6 +86,7 @@ const {
8586 testOnlyFlag,
8687} = parseCommandLine ( ) ;
8788let kResistStopPropagation ;
89+ let assertObj ;
8890let findSourceMap ;
8991
9092const kRunOnceOptions = { __proto__ : null , preserveReturnValue : true } ;
@@ -97,6 +99,19 @@ function lazyFindSourceMap(file) {
9799 return findSourceMap ( file ) ;
98100}
99101
102+ function lazyAssertObject ( ) {
103+ if ( assertObj === undefined ) {
104+ assertObj = new SafeMap ( ) ;
105+ const assert = require ( 'assert' ) ;
106+ for ( const { 0 : key , 1 : value } of ObjectEntries ( assert ) ) {
107+ if ( typeof value === 'function' ) {
108+ assertObj . set ( value , key ) ;
109+ }
110+ }
111+ }
112+ return assertObj ;
113+ }
114+
100115function stopTest ( timeout , signal ) {
101116 const deferred = createDeferredPromise ( ) ;
102117 const abortListener = addAbortListener ( signal , deferred . resolve ) ;
@@ -136,7 +151,25 @@ function stopTest(timeout, signal) {
136151 return deferred . promise ;
137152}
138153
154+ class TestPlan {
155+ constructor ( count ) {
156+ validateUint32 ( count , 'count' , 0 ) ;
157+ this . expected = count ;
158+ this . actual = 0 ;
159+ }
160+
161+ check ( ) {
162+ if ( this . actual !== this . expected ) {
163+ throw new ERR_TEST_FAILURE (
164+ `plan expected ${ this . expected } assertions but received ${ this . actual } ` ,
165+ kTestCodeFailure ,
166+ ) ;
167+ }
168+ }
169+ }
170+
139171class TestContext {
172+ #assert;
140173 #test;
141174
142175 constructor ( test ) {
@@ -163,6 +196,36 @@ class TestContext {
163196 this . #test. diagnostic ( message ) ;
164197 }
165198
199+ plan ( count ) {
200+ if ( this . #test. plan !== null ) {
201+ throw new ERR_TEST_FAILURE (
202+ 'cannot set plan more than once' ,
203+ kTestCodeFailure ,
204+ ) ;
205+ }
206+
207+ this . #test. plan = new TestPlan ( count ) ;
208+ }
209+
210+ get assert ( ) {
211+ if ( this . #assert === undefined ) {
212+ const { plan } = this . #test;
213+ const assertions = lazyAssertObject ( ) ;
214+ const assert = { __proto__ : null } ;
215+
216+ this . #assert = assert ;
217+ for ( const { 0 : method , 1 : name } of assertions . entries ( ) ) {
218+ assert [ name ] = ( ...args ) => {
219+ if ( plan !== null ) {
220+ plan . actual ++ ;
221+ }
222+ return ReflectApply ( method , assert , args ) ;
223+ } ;
224+ }
225+ }
226+ return this . #assert;
227+ }
228+
166229 get mock ( ) {
167230 this . #test. mock ??= new MockTracker ( ) ;
168231 return this . #test. mock ;
@@ -186,6 +249,11 @@ class TestContext {
186249 loc : getCallerLocation ( ) ,
187250 } ;
188251
252+ const { plan } = this . #test;
253+ if ( plan !== null ) {
254+ plan . actual ++ ;
255+ }
256+
189257 const subtest = this . #test. createSubtest (
190258 // eslint-disable-next-line no-use-before-define
191259 Test , name , options , fn , overrides ,
@@ -240,7 +308,7 @@ class Test extends AsyncResource {
240308 super ( 'Test' ) ;
241309
242310 let { fn, name, parent, skip } = options ;
243- const { concurrency, loc, only, timeout, todo, signal } = options ;
311+ const { concurrency, loc, only, timeout, todo, signal, plan } = options ;
244312
245313 if ( typeof fn !== 'function' ) {
246314 fn = noop ;
@@ -351,6 +419,8 @@ class Test extends AsyncResource {
351419 this . fn = fn ;
352420 this . harness = null ; // Configured on the root test by the test harness.
353421 this . mock = null ;
422+ this . plan = null ;
423+ this . expectedAssertions = plan ;
354424 this . cancelled = false ;
355425 this . skipped = skip !== undefined && skip !== false ;
356426 this . isTodo = todo !== undefined && todo !== false ;
@@ -643,6 +713,11 @@ class Test extends AsyncResource {
643713
644714 const hookArgs = this . getRunArgs ( ) ;
645715 const { args, ctx } = hookArgs ;
716+
717+ if ( this . plan === null && this . expectedAssertions ) {
718+ ctx . plan ( this . expectedAssertions ) ;
719+ }
720+
646721 const after = async ( ) => {
647722 if ( this . hooks . after . length > 0 ) {
648723 await this . runHook ( 'after' , hookArgs ) ;
@@ -694,7 +769,7 @@ class Test extends AsyncResource {
694769 this . postRun ( ) ;
695770 return ;
696771 }
697-
772+ this . plan ?. check ( ) ;
698773 this . pass ( ) ;
699774 await afterEach ( ) ;
700775 await after ( ) ;
0 commit comments