33package executor
44
55import (
6+ "fmt"
7+ "strings"
68 "testing"
79
810 "github.com/mendixlabs/mxcli/mdl/ast"
@@ -186,6 +188,22 @@ func TestEnumSplitAllCasesReturnWithoutElseDoesNotCreateFallthrough(t *testing.T
186188 }
187189}
188190
191+ func TestEnumSplitBuilderRejectsMoreThanSupportedBranches (t * testing.T ) {
192+ fb := & flowBuilder {
193+ spacing : HorizontalSpacing ,
194+ measurer : & layoutMeasurer {},
195+ }
196+
197+ if id := fb .addEnumSplit (enumSplitWithBranchCount (maxEnumSplitBranches + 1 )); id != "" {
198+ t .Fatalf ("unsupported enum split returned split ID %q" , id )
199+ }
200+
201+ errors := strings .Join (fb .GetErrors (), "\n " )
202+ if ! strings .Contains (errors , "enum split has 17 branches; at most 16 branches are supported" ) {
203+ t .Fatalf ("expected unsupported branch count error, got %q" , errors )
204+ }
205+ }
206+
189207func objectByID (objects []microflows.MicroflowObject , id model.ID ) microflows.MicroflowObject {
190208 for _ , obj := range objects {
191209 if obj .GetID () == id {
@@ -211,3 +229,79 @@ func logActivityHasMessage(obj microflows.MicroflowObject, message string) bool
211229 }
212230 return false
213231}
232+
233+ func enumSplitWithBranchCount (count int ) * ast.EnumSplitStmt {
234+ cases := make ([]ast.EnumSplitCase , 0 , count )
235+ for i := 0 ; i < count ; i ++ {
236+ cases = append (cases , ast.EnumSplitCase {
237+ Value : fmt .Sprintf ("Value%d" , i + 1 ),
238+ Body : []ast.MicroflowStatement {
239+ & ast.LogStmt {Level : ast .LogInfo , Message : & ast.LiteralExpr {Kind : ast .LiteralString , Value : "branch" }},
240+ },
241+ })
242+ }
243+ return & ast.EnumSplitStmt {
244+ Variable : "SyntheticStatus" ,
245+ Cases : cases ,
246+ }
247+ }
248+
249+ // TestEnumSplitBuilderPreservesFirstStatementAnchor guards against silent
250+ // loss of @anchor(from:..., to:...) on the first statement inside an enum
251+ // split case. Before the fix the enum split builder never read
252+ // stmtOwnAnchor(stmt) for case bodies, so any round-tripped anchor dropped
253+ // on re-exec — describe → exec → describe lost the FlowAnchor entirely.
254+ func TestEnumSplitBuilderPreservesFirstStatementAnchor (t * testing.T ) {
255+ fb := & flowBuilder {
256+ spacing : HorizontalSpacing ,
257+ measurer : & layoutMeasurer {},
258+ }
259+
260+ // @anchor(to: bottom) on the first case statement — bottom is a
261+ // non-default destination anchor (AnchorSideBottom == 2) so we can
262+ // distinguish it from the layout default.
263+ anchor := & ast.FlowAnchors {
264+ From : ast .AnchorSideUnset ,
265+ To : ast .AnchorSideBottom ,
266+ }
267+ fb .addEnumSplit (& ast.EnumSplitStmt {
268+ Variable : "Status" ,
269+ Cases : []ast.EnumSplitCase {
270+ {
271+ Values : []string {"Open" },
272+ Body : []ast.MicroflowStatement {
273+ & ast.LogStmt {
274+ Level : ast .LogInfo ,
275+ Message : & ast.LiteralExpr {Kind : ast .LiteralString , Value : "open" },
276+ Annotations : & ast.ActivityAnnotations {Anchor : anchor },
277+ },
278+ },
279+ },
280+ },
281+ })
282+
283+ var split * microflows.ExclusiveSplit
284+ for _ , obj := range fb .objects {
285+ if s , ok := obj .(* microflows.ExclusiveSplit ); ok {
286+ split = s
287+ break
288+ }
289+ }
290+ if split == nil {
291+ t .Fatal ("expected ExclusiveSplit" )
292+ }
293+
294+ var firstCaseFlow * microflows.SequenceFlow
295+ for _ , f := range fb .flows {
296+ if f .OriginID == split .ID {
297+ firstCaseFlow = f
298+ }
299+ }
300+ if firstCaseFlow == nil {
301+ t .Fatal ("expected split→case flow" )
302+ }
303+ if firstCaseFlow .DestinationConnectionIndex != int (ast .AnchorSideBottom ) {
304+ t .Errorf ("DestinationConnectionIndex = %d, want %d — @anchor(to: bottom) was dropped" ,
305+ firstCaseFlow .DestinationConnectionIndex , int (ast .AnchorSideBottom ))
306+ }
307+ }
0 commit comments