@@ -150,6 +150,92 @@ func TestTraverseFlow_InheritanceSplitPreservesExplicitCaseOrder(t *testing.T) {
150150 }
151151}
152152
153+ func TestTraverseFlow_NestedInheritanceSplitKeepsParentTailOutsideCase (t * testing.T ) {
154+ e := newTestExecutor ()
155+ entityID := mkID ("entity-specialized" )
156+
157+ activityMap := map [model.ID ]microflows.MicroflowObject {
158+ mkID ("start" ): & microflows.StartEvent {BaseMicroflowObject : mkObj ("start" )},
159+ mkID ("init" ): & microflows.ActionActivity {
160+ BaseActivity : microflows.BaseActivity {BaseMicroflowObject : mkObj ("init" )},
161+ Action : & microflows.CreateVariableAction {
162+ VariableName : "TokenValue" ,
163+ InitialValue : "''" ,
164+ },
165+ },
166+ mkID ("outer_split" ): & microflows.ExclusiveSplit {
167+ BaseMicroflowObject : mkObj ("outer_split" ),
168+ SplitCondition : & microflows.ExpressionSplitCondition {Expression : "$UseToken" },
169+ },
170+ mkID ("before_type_split" ): & microflows.ActionActivity {
171+ BaseActivity : microflows.BaseActivity {BaseMicroflowObject : mkObj ("before_type_split" )},
172+ Action : & microflows.LogMessageAction {LogLevel : "Info" , LogNodeName : "'App'" , MessageTemplate : & model.Text {Translations : map [string ]string {"en_US" : "before type split" }}},
173+ },
174+ mkID ("type_split" ): & microflows.InheritanceSplit {
175+ BaseMicroflowObject : mkObj ("type_split" ),
176+ VariableName : "Input" ,
177+ },
178+ mkID ("set_token" ): & microflows.ActionActivity {
179+ BaseActivity : microflows.BaseActivity {BaseMicroflowObject : mkObj ("set_token" )},
180+ Action : & microflows.ChangeVariableAction {VariableName : "TokenValue" , Value : "$Input/Value" },
181+ },
182+ mkID ("failed_log" ): & microflows.ActionActivity {
183+ BaseActivity : microflows.BaseActivity {BaseMicroflowObject : mkObj ("failed_log" )},
184+ Action : & microflows.LogMessageAction {LogLevel : "Info" , LogNodeName : "'App'" , MessageTemplate : & model.Text {Translations : map [string ]string {"en_US" : "no token" }}},
185+ },
186+ mkID ("failed_return" ): & microflows.EndEvent {
187+ BaseMicroflowObject : mkObj ("failed_return" ),
188+ ReturnValue : "empty" ,
189+ },
190+ mkID ("outer_merge" ): & microflows.ExclusiveMerge {BaseMicroflowObject : mkObj ("outer_merge" )},
191+ mkID ("tail" ): & microflows.ActionActivity {
192+ BaseActivity : microflows.BaseActivity {BaseMicroflowObject : mkObj ("tail" )},
193+ Action : & microflows.LogMessageAction {LogLevel : "Info" , LogNodeName : "'App'" , MessageTemplate : & model.Text {Translations : map [string ]string {"en_US" : "tail after split" }}},
194+ },
195+ mkID ("end" ): & microflows.EndEvent {
196+ BaseMicroflowObject : mkObj ("end" ),
197+ ReturnValue : "'ok'" ,
198+ },
199+ }
200+ flowsByOrigin := map [model.ID ][]* microflows.SequenceFlow {
201+ mkID ("start" ): {mkFlow ("start" , "init" )},
202+ mkID ("init" ): {mkFlow ("init" , "outer_split" )},
203+ mkID ("outer_split" ): {
204+ mkBranchFlow ("outer_split" , "before_type_split" , & microflows.ExpressionCase {Expression : "true" }),
205+ mkBranchFlow ("outer_split" , "outer_merge" , & microflows.ExpressionCase {Expression : "false" }),
206+ },
207+ mkID ("before_type_split" ): {mkFlow ("before_type_split" , "type_split" )},
208+ mkID ("type_split" ): {
209+ mkBranchFlow ("type_split" , "set_token" , & microflows.InheritanceCase {EntityID : entityID }),
210+ mkBranchFlow ("type_split" , "failed_log" , & microflows.InheritanceCase {}),
211+ },
212+ mkID ("set_token" ): {mkFlow ("set_token" , "outer_merge" )},
213+ mkID ("failed_log" ): {mkFlow ("failed_log" , "failed_return" )},
214+ mkID ("outer_merge" ): {mkFlow ("outer_merge" , "tail" )},
215+ mkID ("tail" ): {mkFlow ("tail" , "end" )},
216+ }
217+ splitMergeMap := map [model.ID ]model.ID {mkID ("outer_split" ): mkID ("outer_merge" )}
218+ entityNames := map [model.ID ]string {entityID : "Sample.SpecializedInput" }
219+
220+ var lines []string
221+ visited := make (map [model.ID ]bool )
222+ e .traverseFlow (mkID ("start" ), activityMap , flowsByOrigin , splitMergeMap , visited , entityNames , nil , & lines , 0 , nil , 0 , nil )
223+
224+ out := strings .Join (lines , "\n " )
225+ tail := strings .Index (out , "tail after split" )
226+ endSplit := strings .Index (out , "end split;" )
227+ endIf := strings .Index (out , "end if;" )
228+ if tail == - 1 {
229+ t .Fatalf ("expected parent tail after nested inheritance split:\n %s" , out )
230+ }
231+ if endSplit == - 1 || tail < endSplit {
232+ t .Fatalf ("parent tail must not be emitted inside the inheritance case:\n %s" , out )
233+ }
234+ if endIf == - 1 || tail < endIf {
235+ t .Fatalf ("parent tail must remain after the outer IF closes:\n %s" , out )
236+ }
237+ }
238+
153239func TestLastStmtIsReturn_InheritanceSplitAllBranchesReturn (t * testing.T ) {
154240 body := []ast.MicroflowStatement {
155241 & ast.InheritanceSplitStmt {
@@ -219,8 +305,22 @@ func TestBuilder_InheritanceSplitNestedEmptyThenBranchKeepsContinuationCase(t *t
219305 if flow .OriginID != nestedSplitID {
220306 continue
221307 }
222- caseValue , ok := flow .CaseValue .(microflows.EnumerationCase )
223- if ! ok || caseValue .Value != "true" {
308+ // After PR #337 the expression split uses ExpressionCase (pointer or
309+ // value receiver) with Expression="true"/"false" rather than
310+ // EnumerationCase. Accept either representation so the test
311+ // documents the intent without pinning the case shape.
312+ value := ""
313+ switch c := flow .CaseValue .(type ) {
314+ case microflows.EnumerationCase :
315+ value = c .Value
316+ case * microflows.EnumerationCase :
317+ value = c .Value
318+ case microflows.ExpressionCase :
319+ value = c .Expression
320+ case * microflows.ExpressionCase :
321+ value = c .Expression
322+ }
323+ if value != "true" {
224324 continue
225325 }
226326 if _ , ok := objects [flow .DestinationID ].(* microflows.ExclusiveMerge ); ok {
0 commit comments