diff --git a/mdl-examples/bug-tests/316-endevent-no-trailing-newline.mdl b/mdl-examples/bug-tests/316-endevent-no-trailing-newline.mdl new file mode 100644 index 00000000..4c5de79c --- /dev/null +++ b/mdl-examples/bug-tests/316-endevent-no-trailing-newline.mdl @@ -0,0 +1,44 @@ +-- ============================================================================ +-- Bug #316: Synthetic trailing newline appended to EndEvent ReturnValue +-- ============================================================================ +-- +-- Symptom (before fix): +-- The microflow writer appended `"\n"` to every non-empty `ReturnValue` +-- when serializing EndEvent objects. Pristine Studio Pro MPRs do not +-- store that trailing newline, so any roundtrip through mxcli left a +-- detectable BSON drift versus the original .mpr — visible in `mx check` +-- diffs and in `mxcli diff-local` output even when no semantic change +-- had been authored. +-- +-- After fix: +-- `writer_microflow.go` passes `ReturnValue` through directly. Empty +-- return values stay empty; non-empty ones are written without an +-- extra trailing newline. +-- +-- Usage: +-- mxcli exec mdl-examples/bug-tests/316-endevent-no-trailing-newline.mdl -p app.mpr +-- mxcli -p app.mpr -c "describe microflow BugTest316.MF_ReturnExpr" +-- The describe output must round-trip cleanly under describe → exec → describe +-- and (when applied to a pristine Studio Pro project) `mx check` must +-- not report any cosmetic diff in the EndEvent ReturnValue field. +-- ============================================================================ + +create module BugTest316; + +-- Microflow with a non-empty return value: the EndEvent's ReturnValue must +-- not pick up a synthetic trailing newline on serialization. +create microflow BugTest316.MF_ReturnExpr ( + $value: integer +) +returns boolean as $isPositive +begin + return $value > 0; +end; +/ + +-- Microflow with no return value (procedure): EndEvent ReturnValue stays empty. +create microflow BugTest316.MF_NoReturn () +begin + log info node 'BugTest316' 'side effect only'; +end; +/ diff --git a/sdk/mpr/writer_microflow.go b/sdk/mpr/writer_microflow.go index b9cdf507..c09120c1 100644 --- a/sdk/mpr/writer_microflow.go +++ b/sdk/mpr/writer_microflow.go @@ -466,13 +466,10 @@ func serializeMicroflowObject(obj microflows.MicroflowObject) bson.D { } case *microflows.EndEvent: - // Pristine EndEvents always carry `ReturnValue` (empty string for void - // microflows; expression + "\n" when a value is returned). Omitting it - // diverges from the pristine key set on Mx 9 roundtrips. - returnValue := "" - if o.ReturnValue != "" { - returnValue = o.ReturnValue + "\n" - } + // Pristine Mx 9 EndEvents carry `ReturnValue` but not a synthetic trailing + // line break. Adding one can make Studio Pro reject list-return EndEvents + // with CE0117 even though mxcli's parser accepts the expression. + returnValue := o.ReturnValue doc := bson.D{ {Key: "$ID", Value: idToBsonBinary(string(o.ID))}, {Key: "$Type", Value: "Microflows$EndEvent"}, diff --git a/sdk/mpr/writer_microflow_version_test.go b/sdk/mpr/writer_microflow_version_test.go index 783d2eac..fd3c298a 100644 --- a/sdk/mpr/writer_microflow_version_test.go +++ b/sdk/mpr/writer_microflow_version_test.go @@ -79,6 +79,38 @@ func TestSerializeSequenceFlow_Mx10_UsesModernShape(t *testing.T) { } } +func TestSerializeEndEvent_EmptyReturnValueHasNoTrailingLineBreak(t *testing.T) { + end := µflows.EndEvent{ + BaseMicroflowObject: microflows.BaseMicroflowObject{ + BaseElement: model.BaseElement{ID: "end-empty"}, + Position: model.Point{X: 10, Y: 20}, + Size: model.Size{Width: 20, Height: 20}, + }, + ReturnValue: "empty", + } + + doc := serializeMicroflowObject(end) + if got := bsonGetKey(doc, "ReturnValue"); got != "empty" { + t.Fatalf("ReturnValue = %q, want %q", got, "empty") + } +} + +func TestSerializeEndEvent_NonEmptyReturnValueHasNoSyntheticLineBreak(t *testing.T) { + end := µflows.EndEvent{ + BaseMicroflowObject: microflows.BaseMicroflowObject{ + BaseElement: model.BaseElement{ID: "end-result"}, + Position: model.Point{X: 10, Y: 20}, + Size: model.Size{Width: 20, Height: 20}, + }, + ReturnValue: "$Result", + } + + doc := serializeMicroflowObject(end) + if got := bsonGetKey(doc, "ReturnValue"); got != "$Result" { + t.Fatalf("ReturnValue = %q, want %q", got, "$Result") + } +} + func TestSerializeAnnotationFlow_VersionShapes(t *testing.T) { af := µflows.AnnotationFlow{ BaseElement: model.BaseElement{ID: "af-1"},