diff --git a/mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl b/mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl new file mode 100644 index 00000000..ee58b6a2 --- /dev/null +++ b/mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl @@ -0,0 +1,33 @@ +-- ============================================================================ +-- Bug #339: Empty change-object actions must refresh in client +-- ============================================================================ +-- +-- Symptom (before fix): +-- Rebuilding an empty change-object activity from MDL wrote a non-committing +-- ChangeObjectAction with no member changes and RefreshInClient=false. +-- Studio Pro / mx check rejected that activity with CE0032: +-- "Change action has no items specified and does not commit the object." +-- +-- After fix: +-- `change $Object;` is serialized as a refresh-only change action by setting +-- RefreshInClient=true. Although the CE0032 text mentions only items/commit, +-- empirical mx check validation accepts refresh as the third valid escape. +-- +-- Usage: +-- mxcli exec mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl -p app.mpr +-- mx check app.mpr +-- Expected: 0 new CE0032 errors for BugTest339.MF_RefreshOnlyChange. +-- ============================================================================ + +create module BugTest339; + +create entity BugTest339.Item ( + Name: String(100) +); + +create microflow BugTest339.MF_RefreshOnlyChange () +begin + $Item = create BugTest339.Item; + change $Item; +end; +/ diff --git a/mdl/executor/bugfix_regression_test.go b/mdl/executor/bugfix_regression_test.go index 0e1ed366..a3feef37 100644 --- a/mdl/executor/bugfix_regression_test.go +++ b/mdl/executor/bugfix_regression_test.go @@ -675,6 +675,27 @@ func TestCallMicroflowResultType_ResolvesSubsequentChangeMember(t *testing.T) { } } +func TestEmptyChangeObjectRefreshesInClient(t *testing.T) { + fb := &flowBuilder{posX: 100, posY: 100, spacing: HorizontalSpacing} + + id := fb.addChangeObjectAction(&ast.ChangeObjectStmt{Variable: "Object"}) + if id == "" || len(fb.objects) != 1 { + t.Fatalf("expected one change object activity, got id=%q objects=%d", id, len(fb.objects)) + } + + activity, ok := fb.objects[0].(*microflows.ActionActivity) + if !ok { + t.Fatalf("object type = %T, want *microflows.ActionActivity", fb.objects[0]) + } + action, ok := activity.Action.(*microflows.ChangeObjectAction) + if !ok { + t.Fatalf("action type = %T, want *microflows.ChangeObjectAction", activity.Action) + } + if !action.RefreshInClient { + t.Fatal("empty change object must refresh in client to remain valid without member changes or commit") + } +} + func TestCallMicroflowUnknownResultTypeStillDeclaresVariable(t *testing.T) { fb := &flowBuilder{ varTypes: map[string]string{"Result": "Old.ModuleEntity"}, diff --git a/mdl/executor/cmd_microflows_builder_actions.go b/mdl/executor/cmd_microflows_builder_actions.go index 9559eea2..5605b12e 100644 --- a/mdl/executor/cmd_microflows_builder_actions.go +++ b/mdl/executor/cmd_microflows_builder_actions.go @@ -240,10 +240,13 @@ func (fb *flowBuilder) addRollbackAction(s *ast.RollbackStmt) model.ID { // addChangeObjectAction creates a CHANGE statement. func (fb *flowBuilder) addChangeObjectAction(s *ast.ChangeObjectStmt) model.ID { action := µflows.ChangeObjectAction{ - BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, - ChangeVariable: s.Variable, - Commit: microflows.CommitTypeNo, - RefreshInClient: false, + BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, + ChangeVariable: s.Variable, + Commit: microflows.CommitTypeNo, + // Studio Pro rejects an empty non-committing change action unless it + // refreshes in client. The CE0032 message mentions only items/commit, + // but mx check accepts RefreshInClient=true as the third valid escape. + RefreshInClient: len(s.Changes) == 0, } // Look up entity type from variable scope