feat(configmanager): add TreeNodeViewModel

This commit is contained in:
Joseph Doherty
2026-01-19 17:51:15 -05:00
parent e9fc764650
commit 8caaa8ee54
2 changed files with 280 additions and 4 deletions
@@ -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
}
/// <summary>
/// ViewModel for a tree node in the configuration tree.
/// This is a stub implementation for Task 11 - full implementation in Task 12.
/// </summary>
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<TreeNodeViewModel> 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;
}
}
@@ -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();
}
}