From 05e4210894a879284ac33b182d412601dc1acd08 Mon Sep 17 00:00:00 2001 From: Edwin Vasquez Date: Tue, 28 Jul 2026 16:38:37 -0500 Subject: [PATCH 1/7] Fix the Save/Save As menu items hardcoded IsEnabled="False" in XAML --- doc/distrib/xml/en-US/DSCoreNodes.xml | 6 +++--- src/DynamoCoreWpf/Views/Core/DynamoView.xaml | 6 ++---- src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs | 11 ----------- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/doc/distrib/xml/en-US/DSCoreNodes.xml b/doc/distrib/xml/en-US/DSCoreNodes.xml index 2650b909aef..23d8cd5005b 100644 --- a/doc/distrib/xml/en-US/DSCoreNodes.xml +++ b/doc/distrib/xml/en-US/DSCoreNodes.xml @@ -353,7 +353,7 @@ - Parse implementation for converting JObject types to specific Dynamo objects (ie Geometry, Color, Images, etc) + Parse implementation for converting JObject types to specific Dynamo objects (ie Geometry, Color, Images, etc) @@ -378,7 +378,7 @@ - Helper function to determine if object can be cached or if it is null, "null" string, or empty list. + Helper function to determine if object can be cached or if it is null, "null" string, or empty list. Object to check @@ -485,7 +485,7 @@ Recursive function to try and find a common ancestor between two dynamo types - Climbs up the hierarchical tree of the likelyAncestor until it + Climbs up the hierarchical tree of the likelyAncestor until it Check if this node is derived from the likely ancestor The likely ancestor that the node should be deriving from diff --git a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml index 28bd90a97ed..fa532c6668b 100644 --- a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml +++ b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml @@ -354,13 +354,11 @@ + InputGestureText="Ctrl + S" /> + InputGestureText="Ctrl + Shift + S" /> Date: Tue, 28 Jul 2026 20:27:56 -0500 Subject: [PATCH 2/7] Adding unit tests --- test/DynamoCoreWpfTests/DynamoViewTests.cs | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/DynamoCoreWpfTests/DynamoViewTests.cs b/test/DynamoCoreWpfTests/DynamoViewTests.cs index aa2d494b902..b4224b815eb 100644 --- a/test/DynamoCoreWpfTests/DynamoViewTests.cs +++ b/test/DynamoCoreWpfTests/DynamoViewTests.cs @@ -136,6 +136,31 @@ public void TestHomeWorkspaceClosedBeforeCustomNode() Assert.IsTrue(View.saveButton.IsEnabled); } + [Test] + public void WhenDynamoLaunchesThenSaveMenuItemsAreEnabled() + { + // Regression test for DYN-10717: the File > Save and Save As menu items were + // hardcoded IsEnabled="False" in XAML and only re-enabled via events that never + // fire for the workspace created at startup (it is never "Opened" from a file). + Assert.IsTrue(View.saveThisButton.IsEnabled); + Assert.IsTrue(View.saveButton.IsEnabled); + } + + [Test] + public void WhenLastWorkspaceIsClosedThenSaveMenuItemsRemainEnabled() + { + // Regression test for DYN-10717: closing the only open workspace returns to the + // start page; the Save/Save As menu items should remain enabled for the fresh + // workspace left behind (Ctrl+S already worked in this state before the fix). + DynamoModel.IsTestMode = false; + ViewModel.CloseHomeWorkspaceCommand.Execute(null); + DynamoModel.IsTestMode = true; + + Assert.IsTrue(ViewModel.ShowStartPage); + Assert.IsTrue(View.saveThisButton.IsEnabled); + Assert.IsTrue(View.saveButton.IsEnabled); + } + [Test] public void ElementBinding_SaveAs() { From 9db01b0b352df8f2bef59a981b4a09247f6a75ee Mon Sep 17 00:00:00 2001 From: Edwin Vasquez Date: Tue, 28 Jul 2026 22:03:06 -0500 Subject: [PATCH 3/7] Fix unit test --- test/DynamoCoreWpfTests/DynamoViewTests.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/DynamoCoreWpfTests/DynamoViewTests.cs b/test/DynamoCoreWpfTests/DynamoViewTests.cs index b4224b815eb..2950eabd027 100644 --- a/test/DynamoCoreWpfTests/DynamoViewTests.cs +++ b/test/DynamoCoreWpfTests/DynamoViewTests.cs @@ -152,9 +152,16 @@ public void WhenLastWorkspaceIsClosedThenSaveMenuItemsRemainEnabled() // Regression test for DYN-10717: closing the only open workspace returns to the // start page; the Save/Save As menu items should remain enabled for the fresh // workspace left behind (Ctrl+S already worked in this state before the fix). - DynamoModel.IsTestMode = false; - ViewModel.CloseHomeWorkspaceCommand.Execute(null); - DynamoModel.IsTestMode = true; + var wasTestMode = DynamoModel.IsTestMode; + try + { + DynamoModel.IsTestMode = false; + ViewModel.CloseHomeWorkspaceCommand.Execute(null); + } + finally + { + DynamoModel.IsTestMode = wasTestMode; + } Assert.IsTrue(ViewModel.ShowStartPage); Assert.IsTrue(View.saveThisButton.IsEnabled); From eb2fb46c3c7423001bab63be873584ec598dec6e Mon Sep 17 00:00:00 2001 From: Edwin Vasquez Date: Wed, 29 Jul 2026 16:46:24 -0500 Subject: [PATCH 4/7] call OnWorkspaceOpened/raise RequestEnableShortcutBarItems(true) from AddHomeWorkspace() and the tab-close-creates-new-workspace path too --- .../UI/GuidedTour/GuidesManager.cs | 2 ++ .../ViewModels/Core/DynamoViewModel.cs | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs b/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs index ca728b460a7..046d44ecb02 100644 --- a/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs +++ b/src/DynamoCoreWpf/UI/GuidedTour/GuidesManager.cs @@ -164,6 +164,7 @@ internal void LaunchTour(string tourName) GuideFlowEvents.OnGuidedTourStart(tourName); dynamoViewModel.ToastManager?.CloseRealTimeInfoWindow(); dynamoViewModel.OnEnableShortcutBarItems(false); + dynamoViewModel.SetGuidedTourActive(true); Logging.Analytics.TrackScreenView("InteractiveGuidedTours"); Logging.Analytics.TrackEvent(Logging.Actions.Start, Logging.Categories.GuidedTourOperations, Resources.ResourceManager.GetString(currentGuide.GuideNameResource, System.Globalization.CultureInfo.InvariantCulture).Replace("_", ""), currentGuide.SequenceOrder); } @@ -257,6 +258,7 @@ internal void ExitTour() } dynamoViewModel.OnEnableShortcutBarItems(true); + dynamoViewModel.SetGuidedTourActive(false); //Hide guide background overlay guideBackgroundElement.Visibility = Visibility.Hidden; diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs index 46459a29fb1..87542ab28be 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs @@ -72,6 +72,7 @@ public partial class DynamoViewModel : ViewModelBase, IDynamoViewModel private readonly DynamoModel model; private Point transformOrigin; private bool showStartPage = false; + private bool isGuidedTourActive = false; private PreferencesViewModel preferencesViewModel; private string dynamoMLDataPath = string.Empty; private const string dynamoMLDataFileName = "DynamoMLDataPipeline.json"; @@ -3247,7 +3248,21 @@ public void ShowSaveDialogIfNeededAndSaveResult(object parameter) internal bool CanShowSaveDialogIfNeededAndSaveResultCommand(object parameter) { - return true; + return !isGuidedTourActive; + } + + /// + /// Blocks or unblocks the Save/Save As commands (menu items, shortcut bar, and + /// Ctrl+S/Ctrl+Shift+S) while a guided tour is active. Unlike ShowStartPage, this is + /// not tied to workspace-creation flows, so it can safely gate CanExecute without + /// resurrecting DYN-10717 (Save/Save As stuck disabled on a fresh workspace). + /// + /// Whether a guided tour is currently active. + internal void SetGuidedTourActive(bool isActive) + { + isGuidedTourActive = isActive; + ShowSaveDialogIfNeededAndSaveResultCommand.RaiseCanExecuteChanged(); + ShowSaveDialogAndSaveResultCommand.RaiseCanExecuteChanged(); } public void ShowSaveDialogAndSaveResult(object parameter) @@ -3371,7 +3386,7 @@ private bool ShowWarningDialogOnSaveWithUnresolvedIssues() internal bool CanShowSaveDialogAndSaveResult(object parameter) { - return true; + return !isGuidedTourActive; } public void ToggleFullscreenWatchShowing(object parameter) From acdb6916790f5178db0f89a849d36457f9999fab Mon Sep 17 00:00:00 2001 From: Edwin Vasquez Date: Thu, 30 Jul 2026 15:00:46 -0500 Subject: [PATCH 5/7] Sync Save/Save As enablement across menu, hotkey, and toolbar via shared CanExecute, gated on guided tour, Start Page, and workspace dirty state --- .../Controls/ShortcutToolbar.xaml.cs | 14 ------- .../ViewModels/Core/DynamoViewModel.cs | 41 ++++++++++++++++++- .../Views/Core/DynamoView.xaml.cs | 2 - test/DynamoCoreWpfTests/DynamoViewTests.cs | 31 +++++++++----- 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/DynamoCoreWpf/Controls/ShortcutToolbar.xaml.cs b/src/DynamoCoreWpf/Controls/ShortcutToolbar.xaml.cs index 5c154ffe08a..59278bd64bd 100644 --- a/src/DynamoCoreWpf/Controls/ShortcutToolbar.xaml.cs +++ b/src/DynamoCoreWpf/Controls/ShortcutToolbar.xaml.cs @@ -77,7 +77,6 @@ public ShortcutToolbar(DynamoViewModel dynamoViewModel) private void ShortcutToolbar_Loaded(object sender, RoutedEventArgs e) { - IsSaveButtonEnabled = false; IsExportMenuEnabled = false; IsLoginMenuEnabled = !DynamoViewModel.Model.NoNetworkMode; DynamoViewModel.OnRequestShorcutToolbarLoaded(RightMenu.ActualWidth); @@ -245,19 +244,6 @@ internal bool IsOpenButtonEnabled } } - internal bool IsSaveButtonEnabled - { - set - { - Button saveButton = GetButton("SAVE"); - if (saveButton != null) - { - saveButton.IsEnabled = value; - saveButton.Opacity = value ? 1 : 0.5; - } - } - } - internal bool IsLoginMenuEnabled { set diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs index 87542ab28be..ca5078ec86a 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs @@ -73,6 +73,7 @@ public partial class DynamoViewModel : ViewModelBase, IDynamoViewModel private Point transformOrigin; private bool showStartPage = false; private bool isGuidedTourActive = false; + private WorkspaceModel saveCommandsTrackedWorkspace; private PreferencesViewModel preferencesViewModel; private string dynamoMLDataPath = string.Empty; private const string dynamoMLDataFileName = "DynamoMLDataPipeline.json"; @@ -429,6 +430,9 @@ public bool ShowStartPage if(ShowInsertDialogAndInsertResultCommand != null) ShowInsertDialogAndInsertResultCommand.RaiseCanExecuteChanged(); + + ShowSaveDialogIfNeededAndSaveResultCommand?.RaiseCanExecuteChanged(); + ShowSaveDialogAndSaveResultCommand?.RaiseCanExecuteChanged(); } } @@ -899,6 +903,7 @@ protected DynamoViewModel(StartConfiguration startConfiguration) SubscribeModelUiEvents(); SubscribeModelChangedHandlers(); SubscribeModelBackupFileSaveEvent(); + TrackWorkspaceForSaveCommands(model.CurrentWorkspace); InitializeAutomationSettings(startConfiguration.CommandFilePath); @@ -1305,6 +1310,32 @@ private void UnsubscribeModelChangedEvents() model.PropertyChanged -= _model_PropertyChanged; model.WorkspaceCleared -= ModelWorkspaceCleared; model.RequestCancelActiveStateForNode -= this.CancelActiveState; + TrackWorkspaceForSaveCommands(null); + } + + /// + /// Keeps the Save command's CanExecute in sync with the current workspace's dirty + /// flag: unsubscribes from the previously tracked workspace and subscribes to the + /// new one, then re-evaluates CanExecute immediately (the new workspace may already + /// differ in HasUnsavedChanges from the old one). + /// + private void TrackWorkspaceForSaveCommands(WorkspaceModel workspace) + { + if (saveCommandsTrackedWorkspace != null) + saveCommandsTrackedWorkspace.PropertyChanged -= SaveCommandsTrackedWorkspace_PropertyChanged; + + saveCommandsTrackedWorkspace = workspace; + + if (saveCommandsTrackedWorkspace != null) + saveCommandsTrackedWorkspace.PropertyChanged += SaveCommandsTrackedWorkspace_PropertyChanged; + + ShowSaveDialogIfNeededAndSaveResultCommand?.RaiseCanExecuteChanged(); + } + + private void SaveCommandsTrackedWorkspace_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(WorkspaceModel.HasUnsavedChanges)) + ShowSaveDialogIfNeededAndSaveResultCommand.RaiseCanExecuteChanged(); } private void SubscribeDispatcherHandlers() @@ -1483,6 +1514,7 @@ void _model_PropertyChanged(object sender, PropertyChangedEventArgs e) RaisePropertyChanged("ViewingHomespace"); if (this.PublishCurrentWorkspaceCommand != null) this.PublishCurrentWorkspaceCommand.RaiseCanExecuteChanged(); + TrackWorkspaceForSaveCommands(model.CurrentWorkspace); RaisePropertyChanged("IsPanning"); RaisePropertyChanged("IsOrbiting"); //RaisePropertyChanged("RunEnabled"); @@ -3246,9 +3278,14 @@ public void ShowSaveDialogIfNeededAndSaveResult(object parameter) } } + /// + /// "Save" is only meaningful when there is something new to persist, so it is also + /// gated on the current workspace's dirty flag (unlike "Save As", which can always + /// save a copy regardless of whether anything changed). + /// internal bool CanShowSaveDialogIfNeededAndSaveResultCommand(object parameter) { - return !isGuidedTourActive; + return !isGuidedTourActive && !ShowStartPage && (Model.CurrentWorkspace?.HasUnsavedChanges ?? false); } /// @@ -3386,7 +3423,7 @@ private bool ShowWarningDialogOnSaveWithUnresolvedIssues() internal bool CanShowSaveDialogAndSaveResult(object parameter) { - return !isGuidedTourActive; + return !isGuidedTourActive && !ShowStartPage; } public void ToggleFullscreenWatchShowing(object parameter) diff --git a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs index 2b237e628eb..1fb06d80db9 100644 --- a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs +++ b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs @@ -444,7 +444,6 @@ private void DynamoViewModel_RequestEnableShortcutBarItems(bool enable) { shortcutBar.IsNewButtonEnabled = enable; shortcutBar.IsOpenButtonEnabled = enable; - shortcutBar.IsSaveButtonEnabled = enable; shortcutBar.IsLoginMenuEnabled = enable; shortcutBar.IsExportMenuEnabled = enable; shortcutBar.IsNotificationCenterEnabled = enable; @@ -472,7 +471,6 @@ private void OnWorkspaceOpened(WorkspaceModel workspace) } if (!(shortcutBar is null)) { - ShortcutBar.IsSaveButtonEnabled = true; shortcutBar.IsExportMenuEnabled = true; } diff --git a/test/DynamoCoreWpfTests/DynamoViewTests.cs b/test/DynamoCoreWpfTests/DynamoViewTests.cs index 2950eabd027..b843e7e6898 100644 --- a/test/DynamoCoreWpfTests/DynamoViewTests.cs +++ b/test/DynamoCoreWpfTests/DynamoViewTests.cs @@ -137,21 +137,30 @@ public void TestHomeWorkspaceClosedBeforeCustomNode() } [Test] - public void WhenDynamoLaunchesThenSaveMenuItemsAreEnabled() + public void WhenDynamoLaunchesThenSaveAsIsEnabledButSaveIsDisabledUntilDirty() { - // Regression test for DYN-10717: the File > Save and Save As menu items were - // hardcoded IsEnabled="False" in XAML and only re-enabled via events that never - // fire for the workspace created at startup (it is never "Opened" from a file). - Assert.IsTrue(View.saveThisButton.IsEnabled); + // Regression test for DYN-10717, updated per Jira thread consensus (comments on + // 2026-07-29): the bug was the File > Save/Save As menu items being hardcoded + // IsEnabled="False" forever, independent of the underlying command state. The + // fix keeps them driven by a shared CanExecute instead. "Save As" is enabled on + // a fresh workspace; plain "Save" is additionally gated on the dirty flag, since + // there is nothing new to persist until the graph changes. + Assert.IsFalse(View.saveThisButton.IsEnabled); Assert.IsTrue(View.saveButton.IsEnabled); + + ViewModel.HomeSpace.HasUnsavedChanges = true; + + Assert.IsTrue(View.saveThisButton.IsEnabled); } [Test] - public void WhenLastWorkspaceIsClosedThenSaveMenuItemsRemainEnabled() + public void WhenLastWorkspaceIsClosedThenSaveMenuItemsAreDisabled() { - // Regression test for DYN-10717: closing the only open workspace returns to the - // start page; the Save/Save As menu items should remain enabled for the fresh - // workspace left behind (Ctrl+S already worked in this state before the fix). + // Regression test for DYN-10717, updated per Jira thread consensus (comments on + // 2026-07-29): closing the only open workspace returns to the Start Page (Home + // tab), where Save/Save As should be disabled -- confirmed as intended behavior, + // not the bug. The bug was the inconsistency between the menu, hotkey, and + // toolbar, which is now resolved via the shared CanExecute. var wasTestMode = DynamoModel.IsTestMode; try { @@ -164,8 +173,8 @@ public void WhenLastWorkspaceIsClosedThenSaveMenuItemsRemainEnabled() } Assert.IsTrue(ViewModel.ShowStartPage); - Assert.IsTrue(View.saveThisButton.IsEnabled); - Assert.IsTrue(View.saveButton.IsEnabled); + Assert.IsFalse(View.saveThisButton.IsEnabled); + Assert.IsFalse(View.saveButton.IsEnabled); } [Test] From 91f0700fd1998ccc8f36c3054647115248810717 Mon Sep 17 00:00:00 2001 From: Edwin Vasquez Date: Thu, 30 Jul 2026 15:38:41 -0500 Subject: [PATCH 6/7] =?UTF-8?q?Correctly=20reverted=20=E2=80=94=20this=20f?= =?UTF-8?q?ile=20is=20now=20staged=20with=20exactly=20the=20inverse=20of?= =?UTF-8?q?=20the=20whitespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/distrib/xml/en-US/DSCoreNodes.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/distrib/xml/en-US/DSCoreNodes.xml b/doc/distrib/xml/en-US/DSCoreNodes.xml index 23d8cd5005b..2650b909aef 100644 --- a/doc/distrib/xml/en-US/DSCoreNodes.xml +++ b/doc/distrib/xml/en-US/DSCoreNodes.xml @@ -353,7 +353,7 @@ - Parse implementation for converting JObject types to specific Dynamo objects (ie Geometry, Color, Images, etc) + Parse implementation for converting JObject types to specific Dynamo objects (ie Geometry, Color, Images, etc) @@ -378,7 +378,7 @@ - Helper function to determine if object can be cached or if it is null, "null" string, or empty list. + Helper function to determine if object can be cached or if it is null, "null" string, or empty list. Object to check @@ -485,7 +485,7 @@ Recursive function to try and find a common ancestor between two dynamo types - Climbs up the hierarchical tree of the likelyAncestor until it + Climbs up the hierarchical tree of the likelyAncestor until it Check if this node is derived from the likely ancestor The likely ancestor that the node should be deriving from From aed88c5f7d9f9f1bd2a8559e026f9d12a589ead5 Mon Sep 17 00:00:00 2001 From: Edwin Vasquez Date: Fri, 31 Jul 2026 12:46:26 -0500 Subject: [PATCH 7/7] Fixing tests were failing. Add new test focus into the Guided Tour --- test/DynamoCoreWpfTests/DynamoViewTests.cs | 42 +++++++++++++--------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/test/DynamoCoreWpfTests/DynamoViewTests.cs b/test/DynamoCoreWpfTests/DynamoViewTests.cs index b843e7e6898..4966e987fc0 100644 --- a/test/DynamoCoreWpfTests/DynamoViewTests.cs +++ b/test/DynamoCoreWpfTests/DynamoViewTests.cs @@ -139,28 +139,19 @@ public void TestHomeWorkspaceClosedBeforeCustomNode() [Test] public void WhenDynamoLaunchesThenSaveAsIsEnabledButSaveIsDisabledUntilDirty() { - // Regression test for DYN-10717, updated per Jira thread consensus (comments on - // 2026-07-29): the bug was the File > Save/Save As menu items being hardcoded - // IsEnabled="False" forever, independent of the underlying command state. The - // fix keeps them driven by a shared CanExecute instead. "Save As" is enabled on - // a fresh workspace; plain "Save" is additionally gated on the dirty flag, since - // there is nothing new to persist until the graph changes. - Assert.IsFalse(View.saveThisButton.IsEnabled); - Assert.IsTrue(View.saveButton.IsEnabled); + // Regression test for DYN-10717: Save/Save As enablement is driven by a shared CanExecute (menu, hotkey, toolbar all in sync), with "Save" additionally gated on the workspace's dirty flag. + Assert.IsFalse(ViewModel.ShowSaveDialogIfNeededAndSaveResultCommand.CanExecute(null)); + Assert.IsTrue(ViewModel.ShowSaveDialogAndSaveResultCommand.CanExecute(null)); ViewModel.HomeSpace.HasUnsavedChanges = true; - Assert.IsTrue(View.saveThisButton.IsEnabled); + Assert.IsTrue(ViewModel.ShowSaveDialogIfNeededAndSaveResultCommand.CanExecute(null)); } [Test] public void WhenLastWorkspaceIsClosedThenSaveMenuItemsAreDisabled() { - // Regression test for DYN-10717, updated per Jira thread consensus (comments on - // 2026-07-29): closing the only open workspace returns to the Start Page (Home - // tab), where Save/Save As should be disabled -- confirmed as intended behavior, - // not the bug. The bug was the inconsistency between the menu, hotkey, and - // toolbar, which is now resolved via the shared CanExecute. + // Regression test for DYN-10717: closing the only open workspace shows the Start Page, where Save/Save As are intentionally disabled (this is not the bug; the bug was menu/hotkey/toolbar disagreeing with each other). var wasTestMode = DynamoModel.IsTestMode; try { @@ -173,8 +164,27 @@ public void WhenLastWorkspaceIsClosedThenSaveMenuItemsAreDisabled() } Assert.IsTrue(ViewModel.ShowStartPage); - Assert.IsFalse(View.saveThisButton.IsEnabled); - Assert.IsFalse(View.saveButton.IsEnabled); + Assert.IsFalse(ViewModel.ShowSaveDialogIfNeededAndSaveResultCommand.CanExecute(null)); + Assert.IsFalse(ViewModel.ShowSaveDialogAndSaveResultCommand.CanExecute(null)); + } + + [Test] + public void WhenGuidedTourIsActiveThenSaveMenuItemsAreDisabledUntilExit() + { + // Regression test for DYN-10717: Save/Save As must be disabled while a guided tour is active, and re-enabled once it ends. + ViewModel.HomeSpace.HasUnsavedChanges = true; + Assert.IsTrue(ViewModel.ShowSaveDialogIfNeededAndSaveResultCommand.CanExecute(null)); + Assert.IsTrue(ViewModel.ShowSaveDialogAndSaveResultCommand.CanExecute(null)); + + ViewModel.SetGuidedTourActive(true); + + Assert.IsFalse(ViewModel.ShowSaveDialogIfNeededAndSaveResultCommand.CanExecute(null)); + Assert.IsFalse(ViewModel.ShowSaveDialogAndSaveResultCommand.CanExecute(null)); + + ViewModel.SetGuidedTourActive(false); + + Assert.IsTrue(ViewModel.ShowSaveDialogIfNeededAndSaveResultCommand.CanExecute(null)); + Assert.IsTrue(ViewModel.ShowSaveDialogAndSaveResultCommand.CanExecute(null)); } [Test]