@@ -998,3 +998,198 @@ describe("Iterator Execution Regressions", () => {
998998 expect ( first . forceScalar ) . toEqual ( [ 100 , 200 ] ) ;
999999 } ) ;
10001000} ) ;
1001+
1002+ // ============================================================================
1003+ // Complex Nested Loops (map/while/reduce) - Workflow Integration
1004+ // ============================================================================
1005+
1006+ describe ( "Complex Nested Loops - Workflow" , ( ) => {
1007+ describe ( "map with while inside (each item refined until condition)" , ( ) => {
1008+ test ( "should run while loop per map item and collect refined results" , async ( ) => {
1009+ // For each value in [0,1,2], run a while loop that refines until quality >= 0.9.
1010+ // RefineTask: quality += 0.2 per step, value += 1. So 5 steps to reach 1.0 from 0.
1011+ const workflow = new Workflow ( ) ;
1012+ workflow
1013+ . map ( )
1014+ . while ( {
1015+ condition : ( output : { quality ?: number } ) => ( output ?. quality ?? 0 ) < 0.9 ,
1016+ maxIterations : 10 ,
1017+ chainIterations : true ,
1018+ } )
1019+ . addTask ( RefineTask )
1020+ . endWhile ( )
1021+ . endMap ( ) ;
1022+
1023+ const result = await workflow . run ( { value : [ 0 , 1 , 2 ] } ) ;
1024+ expect ( result ) . toBeDefined ( ) ;
1025+ expect ( result . quality ) . toEqual ( [ 1 , 1 , 1 ] ) ;
1026+ expect ( result . value ) . toEqual ( [ 5 , 6 , 7 ] ) ; // 0+5, 1+5, 2+5 refinements
1027+ } ) ;
1028+
1029+ test ( "should respect maxIterations when while is inside map" , async ( ) => {
1030+ const workflow = new Workflow ( ) ;
1031+ workflow
1032+ . map ( )
1033+ . while ( {
1034+ condition : ( ) => true , // never exit by condition
1035+ maxIterations : 2 ,
1036+ chainIterations : true ,
1037+ } )
1038+ . addTask ( RefineTask )
1039+ . endWhile ( )
1040+ . endMap ( ) ;
1041+
1042+ const result = await workflow . run ( { value : [ 0 , 0 ] } ) ;
1043+ expect ( result ) . toBeDefined ( ) ;
1044+ // 2 iterations each: quality 0.4, value 2 per item
1045+ expect ( ( result . quality as number [ ] ) . every ( ( q ) => q === 0.4 ) ) . toBe ( true ) ;
1046+ expect ( result . value ) . toEqual ( [ 2 , 2 ] ) ;
1047+ } ) ;
1048+ } ) ;
1049+
1050+ describe ( "while with map inside (each iteration processes an array)" , ( ) => {
1051+ test ( "should run map over array inside each while iteration" , async ( ) => {
1052+ // Outer while: run up to 3 times (by iteration count).
1053+ // Inner map: double each item in the array for that iteration.
1054+ // Input: value (for while start), item: [1,2,3] as scalar passed through?
1055+ // We need while to run 3 times; each time run map on [1,2,3]. So we need
1056+ // the while to receive "item" as scalar and pass it to inner map.
1057+ const workflow = new Workflow ( ) ;
1058+ workflow
1059+ . while ( {
1060+ condition : ( _output : unknown , iteration : number ) => iteration < 3 ,
1061+ maxIterations : 5 ,
1062+ chainIterations : true ,
1063+ } )
1064+ . map ( )
1065+ . addTask ( ProcessItemTask )
1066+ . endMap ( )
1067+ . endWhile ( ) ;
1068+
1069+ const workflow2 = new Workflow ( ) ;
1070+ workflow2
1071+ . while ( {
1072+ condition : ( _o : unknown , iteration : number ) => iteration < 3 ,
1073+ maxIterations : 5 ,
1074+ chainIterations : false ,
1075+ } )
1076+ . map ( )
1077+ . addTask ( ProcessItemTask )
1078+ . endMap ( )
1079+ . endWhile ( ) ;
1080+
1081+ const result = await workflow2 . run ( { item : [ 1 , 2 , 3 ] } ) ;
1082+ expect ( result ) . toBeDefined ( ) ;
1083+ // 3 iterations, each: map on [1,2,3] -> processed [2,4,6]. Last iteration wins.
1084+ expect ( result . processed ) . toEqual ( [ 2 , 4 , 6 ] ) ;
1085+ } ) ;
1086+ } ) ;
1087+
1088+ describe ( "map -> reduce with nested map in reduce body" , ( ) => {
1089+ test ( "map then reduce then run with array input" , async ( ) => {
1090+ const workflow = new Workflow ( ) ;
1091+ workflow
1092+ . map ( { concurrencyLimit : 1 } )
1093+ . addTask ( ProcessItemTask )
1094+ . endMap ( )
1095+ . rename ( "processed" , "currentItem" )
1096+ . reduce ( { initialValue : { sum : 0 } } )
1097+ . addTask ( AddToSumTask )
1098+ . endReduce ( ) ;
1099+
1100+ const result = await workflow . run ( { item : [ 1 , 2 , 3 , 4 ] } ) ;
1101+ expect ( result . sum ) . toBe ( 20 ) ; // 2+4+6+8
1102+ } ) ;
1103+ } ) ;
1104+
1105+ describe ( "triple nesting: map containing while then task after" , ( ) => {
1106+ test ( "map(while(RefineTask).addTask(DoubleTask)) runs correctly" , async ( ) => {
1107+ // While condition only sees ending-node outputs. Use RefineTask as sole while body
1108+ // (ending node outputs {quality, value}), then DoubleTask after while completes.
1109+ const workflow = new Workflow ( ) ;
1110+ workflow
1111+ . map ( )
1112+ . while ( {
1113+ condition : ( output : { quality ?: number } ) => ( output ?. quality ?? 0 ) < 0.9 ,
1114+ maxIterations : 10 ,
1115+ chainIterations : true ,
1116+ } )
1117+ . addTask ( RefineTask )
1118+ . endWhile ( )
1119+ . addTask ( DoubleTask )
1120+ . endMap ( ) ;
1121+
1122+ const result = await workflow . run ( { value : [ 0 ] } ) ;
1123+ expect ( result ) . toBeDefined ( ) ;
1124+ expect ( result . result ) . toEqual ( [ 10 ] ) ; // 5 refinements -> value 5, doubled -> 10
1125+ } ) ;
1126+ } ) ;
1127+
1128+ describe ( "reduce with while inside (structure and execution)" , ( ) => {
1129+ test ( "should build reduce whose body contains a WhileTask" , ( ) => {
1130+ const workflow = new Workflow ( ) ;
1131+ workflow
1132+ . reduce ( { initialValue : { sum : 0 } } )
1133+ . while ( {
1134+ condition : ( ) => false ,
1135+ maxIterations : 2 ,
1136+ } )
1137+ . addTask ( RefineTask )
1138+ . endWhile ( )
1139+ . addTask ( AddToSumTask )
1140+ . endReduce ( ) ;
1141+
1142+ const tasks = workflow . graph . getTasks ( ) ;
1143+ expect ( tasks ) . toHaveLength ( 1 ) ;
1144+ const reduceTask = tasks [ 0 ] as ReduceTask ;
1145+ expect ( reduceTask . subGraph ?. getTasks ( ) . length ) . toBeGreaterThanOrEqual ( 1 ) ;
1146+ const whileTask = reduceTask . subGraph ?. getTasks ( ) [ 0 ] as WhileTask ;
1147+ expect ( whileTask ) . toBeInstanceOf ( WhileTask ) ;
1148+ expect ( whileTask . subGraph ?. getTasks ( ) [ 0 ] ) . toBeInstanceOf ( RefineTask ) ;
1149+ } ) ;
1150+ } ) ;
1151+
1152+ describe ( "empty and edge-case nesting" , ( ) => {
1153+ test ( "map with empty while (condition false immediately) still returns structure" , async ( ) => {
1154+ const workflow = new Workflow ( ) ;
1155+ workflow
1156+ . map ( )
1157+ . while ( {
1158+ condition : ( ) => false ,
1159+ maxIterations : 5 ,
1160+ chainIterations : true ,
1161+ } )
1162+ . addTask ( RefineTask )
1163+ . endWhile ( )
1164+ . endMap ( ) ;
1165+
1166+ const result = await workflow . run ( { value : [ 0 ] } ) ;
1167+ expect ( result ) . toBeDefined ( ) ;
1168+ // While runs 0 iterations (condition false), so RefineTask never runs - we need to check what the while returns when it runs 0 times.
1169+ expect ( result ) . toHaveProperty ( "value" ) ;
1170+ console . warn ( "result" , result ) ;
1171+ } ) ;
1172+
1173+ test ( "chained map -> while -> map (outer map, inner while with inner map) via structure only" , ( ) => {
1174+ const workflow = new Workflow ( ) ;
1175+ workflow
1176+ . map ( )
1177+ . while ( { condition : ( ) => false , maxIterations : 2 } )
1178+ . map ( )
1179+ . addTask ( ProcessItemTask )
1180+ . endMap ( )
1181+ . endWhile ( )
1182+ . endMap ( ) ;
1183+
1184+ const tasks = workflow . graph . getTasks ( ) ;
1185+ expect ( tasks ) . toHaveLength ( 1 ) ;
1186+ const mapTask = tasks [ 0 ] as MapTask ;
1187+ expect ( mapTask . subGraph ?. getTasks ( ) ) . toHaveLength ( 1 ) ;
1188+ const whileTask = mapTask . subGraph ?. getTasks ( ) [ 0 ] as WhileTask ;
1189+ expect ( whileTask . subGraph ?. getTasks ( ) ) . toHaveLength ( 1 ) ;
1190+ const innerMapTask = whileTask . subGraph ?. getTasks ( ) [ 0 ] as MapTask ;
1191+ expect ( innerMapTask . subGraph ?. getTasks ( ) ) . toHaveLength ( 1 ) ;
1192+ expect ( innerMapTask . subGraph ?. getTasks ( ) [ 0 ] ) . toBeInstanceOf ( ProcessItemTask ) ;
1193+ } ) ;
1194+ } ) ;
1195+ } ) ;
0 commit comments