@@ -7,6 +7,7 @@ import { DOMImplementation, XMLSerializer } from '@xmldom/xmldom'
77import event from '../event.js'
88import store from '../store.js'
99import output from '../output.js'
10+ import container from '../container.js'
1011
1112const defaultConfig = {
1213 outputName : 'report.xml' ,
@@ -18,6 +19,7 @@ const defaultConfig = {
1819}
1920
2021const INVALID_XML_CHARS = new RegExp ( '[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\uFFFE\\uFFFF]' , 'g' )
22+ const SUITE_HOOK_TITLE = / ^ " ( b e f o r e a l l | a f t e r a l l ) " h o o k : /
2123
2224/**
2325 *
@@ -66,6 +68,40 @@ export default function (config = {}) {
6668 config = Object . assign ( { } , defaultConfig , config )
6769
6870 let written = false
71+ let runnerAttached = false
72+ const hookFailures = [ ]
73+ const seenHookFailures = new Set ( )
74+
75+ const attachRunner = ( ) => {
76+ if ( runnerAttached ) return
77+
78+ const mocha = container . mocha ( )
79+ const runner = mocha && ( mocha . runner || mocha . Runner )
80+ if ( ! runner || typeof runner . on !== 'function' ) return
81+
82+ runnerAttached = true
83+ runner . on ( 'fail' , ( failed , err ) => {
84+ if ( ! failed || failed . type !== 'hook' || ! SUITE_HOOK_TITLE . test ( failed . title || '' ) ) return
85+
86+ const suite = failed . parent
87+ const suiteTitle = ( suite && suite . title ) || ''
88+ const key = `${ suiteTitle } ::${ failed . title } `
89+ if ( seenHookFailures . has ( key ) ) return
90+ seenHookFailures . add ( key )
91+
92+ hookFailures . push ( {
93+ title : failed . title || 'hook failed' ,
94+ state : 'failed' ,
95+ err : err || failed . err || { } ,
96+ parent : suite ,
97+ file : failed . file || ( suite && suite . file ) ,
98+ tags : suiteTitle . match ( / @ [ \\ w - ] + / g) || [ ] ,
99+ meta : { } ,
100+ steps : [ ] ,
101+ duration : failed . duration || 0 ,
102+ } )
103+ } )
104+ }
69105
70106 const writeReport = result => {
71107 if ( written ) return
@@ -76,25 +112,29 @@ export default function (config = {}) {
76112 mkdirp . sync ( dir )
77113 const file = path . join ( dir , config . outputName )
78114
79- fs . writeFileSync ( file , buildXml ( result , config ) )
115+ fs . writeFileSync ( file , buildXml ( result , config , hookFailures ) )
80116 output . plugin ( 'junitReporter' , `JUnit report saved to ${ file } ` )
81117 }
82118
119+ event . dispatcher . on ( event . all . before , attachRunner )
120+ event . dispatcher . on ( event . suite . before , attachRunner )
121+ event . dispatcher . on ( event . test . before , attachRunner )
83122 event . dispatcher . on ( event . all . result , writeReport )
84123 event . dispatcher . on ( event . workers . result , writeReport )
85124}
86125
87- function buildXml ( result , config ) {
126+ function buildXml ( result , config , hookFailures = [ ] ) {
88127 const doc = new DOMImplementation ( ) . createDocument ( null , null , null )
89- const suites = groupBySuite ( result . tests )
128+ const allTests = result . tests . concat ( hookFailures )
129+ const suites = groupBySuite ( allTests )
90130
91131 const root = doc . createElement ( 'testsuites' )
92132 setAttr ( root , 'name' , config . testGroupName )
93- setAttr ( root , 'tests' , result . tests . length )
94- setAttr ( root , 'failures' , countState ( result . tests , 'failed' ) )
95- setAttr ( root , 'skipped' , countSkipped ( result . tests ) )
133+ setAttr ( root , 'tests' , allTests . length )
134+ setAttr ( root , 'failures' , countState ( allTests , 'failed' ) )
135+ setAttr ( root , 'skipped' , countSkipped ( allTests ) )
96136 setAttr ( root , 'errors' , 0 )
97- setAttr ( root , 'time' , toSeconds ( sumDuration ( result . tests ) ) )
137+ setAttr ( root , 'time' , toSeconds ( sumDuration ( allTests ) ) )
98138 setAttr ( root , 'timestamp' , toIso ( result . stats && result . stats . start ) )
99139 doc . appendChild ( root )
100140
0 commit comments