@@ -36,8 +36,12 @@ pub(crate) extern "C" fn thunk_reporter_lcov(_closure: *const ClosureHeader, sou
3636}
3737
3838fn reporter_transform ( kind : i32 ) -> f64 {
39- let transform = make_closure ( reporter_transform_chunk as * const u8 , 3 , 1 ) ;
39+ let captures = if kind == REPORTER_SPEC { 2 } else { 1 } ;
40+ let transform = make_closure ( reporter_transform_chunk as * const u8 , 3 , captures) ;
4041 js_closure_set_capture_f64 ( transform, 0 , kind as f64 ) ;
42+ if kind == REPORTER_SPEC {
43+ js_closure_set_capture_f64 ( transform, 1 , undefined_value ( ) ) ;
44+ }
4145 let opts = js_object_alloc ( 0 , 1 ) ;
4246 set_field ( opts, "transform" , boxed_ptr ( transform) ) ;
4347 crate :: node_stream:: js_node_stream_transform_new ( boxed_ptr ( opts) )
@@ -50,7 +54,26 @@ extern "C" fn reporter_transform_chunk(
5054 callback : f64 ,
5155) -> f64 {
5256 let kind = js_closure_get_capture_f64 ( closure, 0 ) as i32 ;
53- let output = format_reporter_event ( kind, chunk) ;
57+ let scope = crate :: gc:: RuntimeHandleScope :: new ( ) ;
58+ let closure_handle = scope. root_raw_mut_ptr ( closure as * mut ClosureHeader ) ;
59+ let chunk_handle = scope. root_nanbox_f64 ( chunk) ;
60+ let stack_handle = ( kind == REPORTER_SPEC )
61+ . then ( || scope. root_nanbox_f64 ( js_closure_get_capture_f64 ( closure, 1 ) ) ) ;
62+ let mut starts = stack_handle
63+ . as_ref ( )
64+ . and_then ( |handle| array_values ( handle. get_nanbox_f64 ( ) ) )
65+ . unwrap_or_default ( ) ;
66+ let output = format_reporter_event ( kind, chunk_handle. get_nanbox_f64 ( ) , & mut starts) ;
67+
68+ if kind == REPORTER_SPEC {
69+ let mut stack = crate :: array:: js_array_alloc ( starts. len ( ) as u32 ) ;
70+ for start in starts {
71+ stack = crate :: array:: js_array_push_f64 ( stack, start) ;
72+ }
73+ closure_handle. with_mut_ptr ( |closure : * mut ClosureHeader | {
74+ js_closure_set_capture_f64 ( closure, 1 , boxed_ptr ( stack) ) ;
75+ } ) ;
76+ }
5477 if !output. is_empty ( ) {
5578 let this = crate :: object:: js_implicit_this_get ( ) ;
5679 let handle = ( this. to_bits ( ) & POINTER_MASK ) as i64 ;
@@ -88,6 +111,18 @@ fn event_data(event: f64) -> f64 {
88111 object_property ( event, b"data" ) . unwrap_or ( undefined_value ( ) )
89112}
90113
114+ fn event_nesting ( data : f64 ) -> usize {
115+ object_property ( data, b"nesting" )
116+ . map ( |value| JSValue :: from_bits ( value. to_bits ( ) ) . to_number ( ) )
117+ . filter ( |value| value. is_finite ( ) && * value > 0.0 )
118+ . map ( |value| value as usize )
119+ . unwrap_or ( 0 )
120+ }
121+
122+ fn event_indent ( data : f64 , width : usize ) -> String {
123+ " " . repeat ( event_nesting ( data) . saturating_mul ( width) )
124+ }
125+
91126fn format_reporter_events ( kind : i32 , events : & [ f64 ] ) -> String {
92127 if kind == REPORTER_LCOV {
93128 return String :: new ( ) ;
@@ -98,8 +133,9 @@ fn format_reporter_events(kind: i32, events: &[f64]) -> String {
98133 } else if kind == REPORTER_JUNIT {
99134 out. push_str ( "<?xml version=\" 1.0\" encoding=\" utf-8\" ?>\n <testsuites>\n " ) ;
100135 }
136+ let mut starts = Vec :: new ( ) ;
101137 for & event in events {
102- out. push_str ( & format_reporter_event ( kind, event) ) ;
138+ out. push_str ( & format_reporter_event ( kind, event, & mut starts ) ) ;
103139 }
104140 if kind == REPORTER_DOT && !out. is_empty ( ) && !out. ends_with ( '\n' ) {
105141 out. push ( '\n' ) ;
@@ -110,34 +146,51 @@ fn format_reporter_events(kind: i32, events: &[f64]) -> String {
110146 out
111147}
112148
113- fn format_reporter_event ( kind : i32 , event : f64 ) -> String {
149+ fn format_reporter_event ( kind : i32 , event : f64 , starts : & mut Vec < f64 > ) -> String {
114150 let Some ( typ) = event_type ( event) else {
115151 return String :: new ( ) ;
116152 } ;
117153 let data = event_data ( event) ;
118154 match kind {
119155 REPORTER_SPEC => match typ. as_str ( ) {
120- "test:pass" => object_string ( data, b"name" )
121- . map ( |name| format ! ( "✔ {name}\n " ) )
122- . unwrap_or_default ( ) ,
156+ "test:start" => {
157+ starts. push ( data) ;
158+ String :: new ( )
159+ }
160+ "test:pass" => {
161+ starts. pop ( ) ;
162+ let mut output = String :: new ( ) ;
163+ for parent in starts. drain ( ..) {
164+ if let Some ( name) = object_string ( parent, b"name" ) {
165+ output. push_str ( & format ! ( "{}▶ {name}\n " , event_indent( parent, 2 ) ) ) ;
166+ }
167+ }
168+ if let Some ( name) = object_string ( data, b"name" ) {
169+ output. push_str ( & format ! ( "{}✔ {name}\n " , event_indent( data, 2 ) ) ) ;
170+ }
171+ output
172+ }
123173 "test:diagnostic" => object_string ( data, b"message" )
124- . map ( |message| format ! ( "ℹ {message}\n " ) )
174+ . map ( |message| format ! ( "{} ℹ {message}\n " , event_indent ( data , 2 ) ) )
125175 . unwrap_or_default ( ) ,
126176 _ => String :: new ( ) ,
127177 } ,
128178 REPORTER_TAP => match typ. as_str ( ) {
129179 "test:start" => object_string ( data, b"name" )
130- . map ( |name| format ! ( "# Subtest: {name}\n " ) )
180+ . map ( |name| format ! ( "{} # Subtest: {name}\n " , event_indent ( data , 4 ) ) )
131181 . unwrap_or_default ( ) ,
132182 "test:pass" => {
133183 let name = object_string ( data, b"name" ) . unwrap_or_default ( ) ;
184+ let indent = event_indent ( data, 4 ) ;
134185 let detail_type = object_property ( data, b"details" )
135186 . and_then ( |details| object_string ( details, b"type" ) )
136187 . unwrap_or_else ( || "test" . to_string ( ) ) ;
137- format ! ( "ok undefined - {name}\n ---\n type: '{detail_type}'\n ...\n " )
188+ format ! (
189+ "{indent}ok undefined - {name}\n {indent} ---\n {indent} type: '{detail_type}'\n {indent} ...\n "
190+ )
138191 }
139192 "test:diagnostic" => object_string ( data, b"message" )
140- . map ( |message| format ! ( "# {message}\n " ) )
193+ . map ( |message| format ! ( "{} # {message}\n " , event_indent ( data , 4 ) ) )
141194 . unwrap_or_default ( ) ,
142195 _ => String :: new ( ) ,
143196 } ,
0 commit comments