@@ -29,24 +29,19 @@ import {
2929 type SampleRow ,
3030} from "../../types/execution" ;
3131
32- // Convert flat test rows (with an optional embedded __row_index__) into the
33- // structured SampleRow[] the summary now carries.
3432function toSampleRows ( rows : Record < string , any > [ ] ) : SampleRow [ ] {
35- return rows . map ( ( row , i ) => {
36- const { __row_index__, ...tuple } = row ;
37- return { rowIndex : typeof __row_index__ === "number" ? __row_index__ : i , tuple } ;
38- } ) ;
33+ return rows . map ( ( tuple , rowIndex ) => ( { rowIndex, tuple } ) ) ;
3934}
4035
41- // Test convenience: accept the (old) flat fields and assemble the structured
42- // OperatorExecutionSummary, so the cases below stay terse.
4336interface OpInfoOverrides {
4437 state ?: OperatorState ;
4538 error ?: string ;
4639 outputTuples ?: number ;
4740 tuplesCount ?: number ;
4841 warnings ?: string [ ] ;
4942 result ?: Record < string , any > [ ] ;
43+ sampleTuples ?: SampleRow [ ] ;
44+ resultMode ?: OperatorResultMode ;
5045}
5146
5247function makeExecutionFailure ( message : string ) : WorkflowFatalError {
@@ -66,11 +61,13 @@ function makeOpInfo(overrides: OpInfoOverrides = {}): OperatorExecutionSummary {
6661 errorMessages : overrides . error ? [ makeExecutionFailure ( overrides . error ) ] : [ ] ,
6762 } ;
6863 // The result summary is present only when the operator produced a result.
69- if ( overrides . result !== undefined ) {
64+ if ( overrides . result !== undefined || overrides . sampleTuples !== undefined ) {
7065 summary . resultSummary = {
71- resultMode : OperatorResultMode . TABLE ,
66+ resultMode : overrides . resultMode ?? OperatorResultMode . TABLE ,
7267 // Non-arrays are passed through to exercise the "(no result data)" guard.
73- sampleTuples : Array . isArray ( overrides . result ) ? toSampleRows ( overrides . result ) : ( overrides . result as any ) ,
68+ sampleTuples :
69+ overrides . sampleTuples ??
70+ ( Array . isArray ( overrides . result ) ? toSampleRows ( overrides . result ) : ( overrides . result as any ) ) ,
7471 tuplesCount : overrides . tuplesCount ?? overrides . outputTuples ?? 0 ,
7572 } ;
7673 }
@@ -125,16 +122,15 @@ describe("formatOperatorResult - table shape and metadata", () => {
125122 expect ( out ) . toContain ( "Output table shape: (999, 2)" ) ;
126123 } ) ;
127124
128- test ( "filters internal __is_visualization__ key from outer column count " , ( ) => {
125+ test ( "counts every result tuple key as a column" , ( ) => {
129126 const out = formatOperatorResult (
130127 "op1" ,
131128 makeOpInfo ( {
132129 outputTuples : 1 ,
133- result : [ { __is_visualization__ : true , "html-content" : "<x/>" } ] ,
130+ result : [ { "html-content" : "<x/>" , label : "chart " } ] ,
134131 } )
135132 ) ;
136- // 1 visible column ("html-content") since __is_visualization__ is filtered.
137- expect ( out ) . toContain ( "Output table shape: (1, 1)" ) ;
133+ expect ( out ) . toContain ( "Output table shape: (1, 2)" ) ;
138134 } ) ;
139135
140136 test ( "appends warnings after metadata lines" , ( ) => {
@@ -155,14 +151,14 @@ describe("formatOperatorResult - table shape and metadata", () => {
155151} ) ;
156152
157153describe ( "formatOperatorResult - visualization rows" , ( ) => {
158- test ( "strips html-content and json-content payloads when row is flagged as visualization" , ( ) => {
154+ test ( "strips html-content and json-content payloads when result mode is visualization" , ( ) => {
159155 const out = formatOperatorResult (
160156 "op1" ,
161157 makeOpInfo ( {
162158 outputTuples : 1 ,
159+ resultMode : OperatorResultMode . VISUALIZATION ,
163160 result : [
164161 {
165- __is_visualization__ : true ,
166162 "html-content" : "<div>hidden</div>" ,
167163 "json-content" : '{"big":1}' ,
168164 label : "chart" ,
@@ -176,32 +172,32 @@ describe("formatOperatorResult - visualization rows", () => {
176172 expect ( out ) . toContain ( "chart" ) ;
177173 } ) ;
178174
179- test ( "__is_visualization__ false leaves the visualization-only fields untouched" , ( ) => {
175+ test ( "table result mode leaves visualization payload fields untouched" , ( ) => {
180176 const out = formatOperatorResult (
181177 "op1" ,
182178 makeOpInfo ( {
183179 outputTuples : 1 ,
184- result : [ { __is_visualization__ : false , "html-content" : "<keep/>" } ] ,
180+ resultMode : OperatorResultMode . TABLE ,
181+ result : [ { "html-content" : "<keep/>" } ] ,
185182 } )
186183 ) ;
187184 expect ( out ) . toContain ( "<keep/>" ) ;
188185 expect ( out ) . not . toContain ( "<skipped: visualization content>" ) ;
189186 } ) ;
190187
191- test ( "__is_visualization__ column is excluded from rendered table body and shape agrees" , ( ) => {
188+ test ( "table rows render all tuple columns and shape agrees" , ( ) => {
192189 const out = formatOperatorResult (
193190 "op1" ,
194191 makeOpInfo ( {
195192 outputTuples : 1 ,
196- result : [ { __is_visualization__ : false , value : 1 } ] ,
193+ result : [ { value : 1 } ] ,
197194 } )
198195 ) ;
199196 const lines = out . split ( "\n" ) ;
200197 expect ( out ) . toContain ( "Output table shape: (1, 1)" ) ;
201198 // Header line is the third line (after brief summary and shape line).
202199 expect ( lines [ 2 ] ) . toBe ( "\tvalue" ) ;
203200 expect ( lines [ 3 ] ) . toBe ( "0\t1" ) ;
204- expect ( out ) . not . toContain ( "__is_visualization__" ) ;
205201 } ) ;
206202} ) ;
207203
@@ -240,14 +236,14 @@ describe("jsonToTableFormat - cell coercion via formatOperatorResult", () => {
240236} ) ;
241237
242238describe ( "jsonToTableFormat - row index gaps" , ( ) => {
243- test ( "inserts ... separator when __row_index__ skips ahead" , ( ) => {
239+ test ( "inserts ... separator when rowIndex skips ahead" , ( ) => {
244240 const out = formatOperatorResult (
245241 "op1" ,
246242 makeOpInfo ( {
247243 outputTuples : 2 ,
248- result : [
249- { __row_index__ : 0 , v : "a" } ,
250- { __row_index__ : 5 , v : "b" } ,
244+ sampleTuples : [
245+ { rowIndex : 0 , tuple : { v : "a" } } ,
246+ { rowIndex : 5 , tuple : { v : "b" } } ,
251247 ] ,
252248 } )
253249 ) ;
@@ -259,22 +255,25 @@ describe("jsonToTableFormat - row index gaps", () => {
259255 expect ( lines [ lines . length - 1 ] ) . toBe ( "5\tb" ) ;
260256 } ) ;
261257
262- test ( "no separator is emitted between consecutive __row_index__ values" , ( ) => {
258+ test ( "no separator is emitted between consecutive rowIndex values" , ( ) => {
263259 const out = formatOperatorResult (
264260 "op1" ,
265261 makeOpInfo ( {
266262 outputTuples : 2 ,
267- result : [
268- { __row_index__ : 0 , v : "a" } ,
269- { __row_index__ : 1 , v : "b" } ,
263+ sampleTuples : [
264+ { rowIndex : 0 , tuple : { v : "a" } } ,
265+ { rowIndex : 1 , tuple : { v : "b" } } ,
270266 ] ,
271267 } )
272268 ) ;
273269 expect ( out ) . not . toContain ( "...\t..." ) ;
274270 } ) ;
275271
276- test ( "non-zero starting __row_index__ does not emit a leading gap marker" , ( ) => {
277- const out = formatOperatorResult ( "op1" , makeOpInfo ( { outputTuples : 1 , result : [ { __row_index__ : 9 , v : "z" } ] } ) ) ;
272+ test ( "non-zero starting rowIndex does not emit a leading gap marker" , ( ) => {
273+ const out = formatOperatorResult (
274+ "op1" ,
275+ makeOpInfo ( { outputTuples : 1 , sampleTuples : [ { rowIndex : 9 , tuple : { v : "z" } } ] } )
276+ ) ;
278277 expect ( out ) . not . toContain ( "...\t..." ) ;
279278 expect ( out . endsWith ( "9\tz" ) ) . toBe ( true ) ;
280279 } ) ;
0 commit comments