diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/TreeNodeViewModel.cs b/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/TreeNodeViewModel.cs
index 1b6f332..f8db87b 100644
--- a/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/TreeNodeViewModel.cs
+++ b/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/TreeNodeViewModel.cs
@@ -2,17 +2,35 @@ using System.Collections.ObjectModel;
namespace JdeScoping.ConfigManager.ViewModels;
+public enum TreeNodeType
+{
+ Folder,
+ SettingsSection,
+ Pipeline
+}
+
+public enum ValidationState
+{
+ Valid,
+ Warning,
+ Error,
+ Unknown
+}
+
///
/// ViewModel for a tree node in the configuration tree.
-/// This is a stub implementation for Task 11 - full implementation in Task 12.
///
public class TreeNodeViewModel : ViewModelBase
{
private bool _isModified;
+ private bool _isExpanded;
+ private bool _isSelected;
+ private ValidationState _validationState = ValidationState.Unknown;
- public string Name { get; set; } = string.Empty;
- public string Icon { get; set; } = string.Empty;
- public string StatusIcon { get; set; } = string.Empty;
+ public string Name { get; }
+ public string Icon { get; }
+ public TreeNodeType NodeType { get; }
+ public string? SectionKey { get; init; }
public ObservableCollection Children { get; } = [];
public bool IsModified
@@ -20,4 +38,41 @@ public class TreeNodeViewModel : ViewModelBase
get => _isModified;
set => SetProperty(ref _isModified, value);
}
+
+ public bool IsExpanded
+ {
+ get => _isExpanded;
+ set => SetProperty(ref _isExpanded, value);
+ }
+
+ public bool IsSelected
+ {
+ get => _isSelected;
+ set => SetProperty(ref _isSelected, value);
+ }
+
+ public ValidationState ValidationState
+ {
+ get => _validationState;
+ set
+ {
+ if (SetProperty(ref _validationState, value))
+ OnPropertyChanged(nameof(StatusIcon));
+ }
+ }
+
+ public string StatusIcon => ValidationState switch
+ {
+ ValidationState.Valid => "✓",
+ ValidationState.Warning => "⚠",
+ ValidationState.Error => "✗",
+ _ => ""
+ };
+
+ public TreeNodeViewModel(string name, string icon, TreeNodeType nodeType)
+ {
+ Name = name;
+ Icon = icon;
+ NodeType = nodeType;
+ }
}
diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/TreeNodeViewModelTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/TreeNodeViewModelTests.cs
new file mode 100644
index 0000000..2a071df
--- /dev/null
+++ b/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/TreeNodeViewModelTests.cs
@@ -0,0 +1,221 @@
+using JdeScoping.ConfigManager.ViewModels;
+
+namespace JdeScoping.ConfigManager.Tests.ViewModels;
+
+public class TreeNodeViewModelTests
+{
+ [Fact]
+ public void Constructor_SetsProperties()
+ {
+ // Arrange & Act
+ var node = new TreeNodeViewModel("DataSync", "⟳", TreeNodeType.SettingsSection);
+
+ // Assert
+ node.Name.ShouldBe("DataSync");
+ node.Icon.ShouldBe("⟳");
+ node.NodeType.ShouldBe(TreeNodeType.SettingsSection);
+ }
+
+ [Fact]
+ public void IsModified_WhenSet_RaisesPropertyChanged()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+ var propertyChangedRaised = false;
+ node.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(TreeNodeViewModel.IsModified))
+ propertyChangedRaised = true;
+ };
+
+ // Act
+ node.IsModified = true;
+
+ // Assert
+ propertyChangedRaised.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void IsExpanded_WhenSet_RaisesPropertyChanged()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+ var propertyChangedRaised = false;
+ node.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(TreeNodeViewModel.IsExpanded))
+ propertyChangedRaised = true;
+ };
+
+ // Act
+ node.IsExpanded = true;
+
+ // Assert
+ propertyChangedRaised.ShouldBeTrue();
+ node.IsExpanded.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void IsSelected_WhenSet_RaisesPropertyChanged()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+ var propertyChangedRaised = false;
+ node.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(TreeNodeViewModel.IsSelected))
+ propertyChangedRaised = true;
+ };
+
+ // Act
+ node.IsSelected = true;
+
+ // Assert
+ propertyChangedRaised.ShouldBeTrue();
+ node.IsSelected.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void ValidationState_WhenSetToValid_StatusIconIsCheckmark()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+
+ // Act
+ node.ValidationState = ValidationState.Valid;
+
+ // Assert
+ node.StatusIcon.ShouldBe("✓");
+ }
+
+ [Fact]
+ public void ValidationState_WhenSetToWarning_StatusIconIsWarning()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+
+ // Act
+ node.ValidationState = ValidationState.Warning;
+
+ // Assert
+ node.StatusIcon.ShouldBe("⚠");
+ }
+
+ [Fact]
+ public void ValidationState_WhenSetToError_StatusIconIsX()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+
+ // Act
+ node.ValidationState = ValidationState.Error;
+
+ // Assert
+ node.StatusIcon.ShouldBe("✗");
+ }
+
+ [Fact]
+ public void ValidationState_WhenSetToUnknown_StatusIconIsEmpty()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+
+ // Act
+ node.ValidationState = ValidationState.Unknown;
+
+ // Assert
+ node.StatusIcon.ShouldBe("");
+ }
+
+ [Fact]
+ public void ValidationState_WhenChanged_RaisesPropertyChangedForStatusIcon()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+ var statusIconPropertyChanged = false;
+ node.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(TreeNodeViewModel.StatusIcon))
+ statusIconPropertyChanged = true;
+ };
+
+ // Act
+ node.ValidationState = ValidationState.Valid;
+
+ // Assert
+ statusIconPropertyChanged.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void SectionKey_CanBeSetViaInitializer()
+ {
+ // Arrange & Act
+ var node = new TreeNodeViewModel("DataSync", "⟳", TreeNodeType.SettingsSection)
+ {
+ SectionKey = "DataSync"
+ };
+
+ // Assert
+ node.SectionKey.ShouldBe("DataSync");
+ }
+
+ [Fact]
+ public void Children_IsInitialized()
+ {
+ // Arrange & Act
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+
+ // Assert
+ node.Children.ShouldNotBeNull();
+ node.Children.ShouldBeEmpty();
+ }
+
+ [Fact]
+ public void Children_CanAddChildNodes()
+ {
+ // Arrange
+ var parent = new TreeNodeViewModel("Settings", "📁", TreeNodeType.Folder);
+ var child = new TreeNodeViewModel("DataSync", "⟳", TreeNodeType.SettingsSection);
+
+ // Act
+ parent.Children.Add(child);
+
+ // Assert
+ parent.Children.Count.ShouldBe(1);
+ parent.Children[0].ShouldBe(child);
+ }
+
+ [Theory]
+ [InlineData(TreeNodeType.Folder)]
+ [InlineData(TreeNodeType.SettingsSection)]
+ [InlineData(TreeNodeType.Pipeline)]
+ public void NodeType_CanBeSetForAllTypes(TreeNodeType nodeType)
+ {
+ // Arrange & Act
+ var node = new TreeNodeViewModel("Test", "📁", nodeType);
+
+ // Assert
+ node.NodeType.ShouldBe(nodeType);
+ }
+
+ [Fact]
+ public void IsModified_WhenSetToSameValue_DoesNotRaisePropertyChanged()
+ {
+ // Arrange
+ var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
+ node.IsModified = true;
+
+ var propertyChangedRaised = false;
+ node.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(TreeNodeViewModel.IsModified))
+ propertyChangedRaised = true;
+ };
+
+ // Act
+ node.IsModified = true; // Same value
+
+ // Assert
+ propertyChangedRaised.ShouldBeFalse();
+ }
+}