@@ -3,6 +3,7 @@ const Emittery = require('emittery');
33const matcher = require ( 'matcher' ) ;
44const ContextRef = require ( './context-ref' ) ;
55const createChain = require ( './create-chain' ) ;
6+ const parseTestArgs = require ( './parse-test-args' ) ;
67const snapshotManager = require ( './snapshot-manager' ) ;
78const serializeError = require ( './serialize-error' ) ;
89const Runnable = require ( './test' ) ;
@@ -11,6 +12,7 @@ class Runner extends Emittery {
1112 constructor ( options = { } ) {
1213 super ( ) ;
1314
15+ this . experiments = options . experiments || { } ;
1416 this . failFast = options . failFast === true ;
1517 this . failWithoutAssertions = options . failWithoutAssertions !== false ;
1618 this . file = options . file ;
@@ -39,12 +41,21 @@ class Runner extends Emittery {
3941 } ;
4042
4143 const uniqueTestTitles = new Set ( ) ;
44+ this . registerUniqueTitle = title => {
45+ if ( uniqueTestTitles . has ( title ) ) {
46+ return false ;
47+ }
48+
49+ uniqueTestTitles . add ( title ) ;
50+ return true ;
51+ } ;
52+
4253 let hasStarted = false ;
4354 let scheduledStart = false ;
4455 const meta = Object . freeze ( {
4556 file : options . file
4657 } ) ;
47- this . chain = createChain ( ( metadata , args ) => { // eslint-disable-line complexity
58+ this . chain = createChain ( ( metadata , testArgs ) => { // eslint-disable-line complexity
4859 if ( hasStarted ) {
4960 throw new Error ( 'All tests and hooks must be declared synchronously in your test file, and cannot be nested within other tests or hooks.' ) ;
5061 }
@@ -57,40 +68,33 @@ class Runner extends Emittery {
5768 } ) ;
5869 }
5970
60- const specifiedTitle = typeof args [ 0 ] === 'string' ?
61- args . shift ( ) :
62- undefined ;
63- const implementations = Array . isArray ( args [ 0 ] ) ?
64- args . shift ( ) :
65- args . splice ( 0 , 1 ) ;
71+ const { args, buildTitle, implementations, rawTitle} = parseTestArgs ( testArgs ) ;
6672
6773 if ( metadata . todo ) {
6874 if ( implementations . length > 0 ) {
6975 throw new TypeError ( '`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.' ) ;
7076 }
7177
72- if ( specifiedTitle === undefined || specifiedTitle === '' ) {
78+ if ( ! rawTitle ) { // Either undefined or a string.
7379 throw new TypeError ( '`todo` tests require a title' ) ;
7480 }
7581
76- if ( uniqueTestTitles . has ( specifiedTitle ) ) {
77- throw new Error ( `Duplicate test title: ${ specifiedTitle } ` ) ;
78- } else {
79- uniqueTestTitles . add ( specifiedTitle ) ;
82+ if ( ! this . registerUniqueTitle ( rawTitle ) ) {
83+ throw new Error ( `Duplicate test title: ${ rawTitle } ` ) ;
8084 }
8185
8286 if ( this . match . length > 0 ) {
8387 // --match selects TODO tests.
84- if ( matcher ( [ specifiedTitle ] , this . match ) . length === 1 ) {
88+ if ( matcher ( [ rawTitle ] , this . match ) . length === 1 ) {
8589 metadata . exclusive = true ;
8690 this . runOnlyExclusive = true ;
8791 }
8892 }
8993
90- this . tasks . todo . push ( { title : specifiedTitle , metadata} ) ;
94+ this . tasks . todo . push ( { title : rawTitle , metadata} ) ;
9195 this . emit ( 'stateChange' , {
9296 type : 'declared-test' ,
93- title : specifiedTitle ,
97+ title : rawTitle ,
9498 knownFailing : false ,
9599 todo : true
96100 } ) ;
@@ -100,15 +104,13 @@ class Runner extends Emittery {
100104 }
101105
102106 for ( const implementation of implementations ) {
103- let title = implementation . title ?
104- implementation . title ( specifiedTitle , ...args ) :
105- specifiedTitle ;
107+ let { title, isSet, isValid, isEmpty} = buildTitle ( implementation ) ;
106108
107- if ( title !== undefined && typeof title !== 'string' ) {
109+ if ( isSet && ! isValid ) {
108110 throw new TypeError ( 'Test & hook titles must be strings' ) ;
109111 }
110112
111- if ( title === undefined || title === '' ) {
113+ if ( isEmpty ) {
112114 if ( metadata . type === 'test' ) {
113115 throw new TypeError ( 'Tests must have a title' ) ;
114116 } else if ( metadata . always ) {
@@ -118,12 +120,8 @@ class Runner extends Emittery {
118120 }
119121 }
120122
121- if ( metadata . type === 'test' ) {
122- if ( uniqueTestTitles . has ( title ) ) {
123- throw new Error ( `Duplicate test title: ${ title } ` ) ;
124- } else {
125- uniqueTestTitles . add ( title ) ;
126- }
123+ if ( metadata . type === 'test' && ! this . registerUniqueTitle ( title ) ) {
124+ throw new Error ( `Duplicate test title: ${ title } ` ) ;
127125 }
128126
129127 const task = {
@@ -162,6 +160,7 @@ class Runner extends Emittery {
162160 todo : false ,
163161 failing : false ,
164162 callback : false ,
163+ inline : false , // Set for attempt metadata created by `t.try()`
165164 always : false
166165 } , meta ) ;
167166 }
@@ -269,6 +268,7 @@ class Runner extends Emittery {
269268 async runHooks ( tasks , contextRef , titleSuffix ) {
270269 const hooks = tasks . map ( task => new Runnable ( {
271270 contextRef,
271+ experiments : this . experiments ,
272272 failWithoutAssertions : false ,
273273 fn : task . args . length === 0 ?
274274 task . implementation :
@@ -309,14 +309,16 @@ class Runner extends Emittery {
309309 // Only run the test if all `beforeEach` hooks passed.
310310 const test = new Runnable ( {
311311 contextRef,
312+ experiments : this . experiments ,
312313 failWithoutAssertions : this . failWithoutAssertions ,
313314 fn : task . args . length === 0 ?
314315 task . implementation :
315316 t => task . implementation . apply ( null , [ t ] . concat ( task . args ) ) ,
316317 compareTestSnapshot : this . boundCompareTestSnapshot ,
317318 updateSnapshots : this . updateSnapshots ,
318319 metadata : task . metadata ,
319- title : task . title
320+ title : task . title ,
321+ registerUniqueTitle : this . registerUniqueTitle
320322 } ) ;
321323
322324 const result = await this . runSingle ( test ) ;
0 commit comments