feat(configmanager): add SearchFormViewModel
Implements Task 18 from phases 7-9 plan. SearchFormViewModel wraps SearchSection model with properties for MaxResultRows, TimeoutSeconds, and MaxConcurrentSearches. Includes full test coverage with 7 tests verifying initialization, two-way binding, change notification, and null argument handling.
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
using JdeScoping.ConfigManager.Models;
|
||||||
|
|
||||||
|
namespace JdeScoping.ConfigManager.ViewModels.Forms;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel for editing Search configuration section.
|
||||||
|
/// </summary>
|
||||||
|
public class SearchFormViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly SearchSection _model;
|
||||||
|
private readonly Action _onChanged;
|
||||||
|
|
||||||
|
public SearchFormViewModel(SearchSection model, Action onChanged)
|
||||||
|
{
|
||||||
|
_model = model ?? throw new ArgumentNullException(nameof(model));
|
||||||
|
_onChanged = onChanged ?? throw new ArgumentNullException(nameof(onChanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of result rows returned by a search.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxResultRows
|
||||||
|
{
|
||||||
|
get => _model.MaxResultRows;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model.MaxResultRows != value)
|
||||||
|
{
|
||||||
|
_model.MaxResultRows = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
_onChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timeout in seconds for search operations.
|
||||||
|
/// </summary>
|
||||||
|
public int TimeoutSeconds
|
||||||
|
{
|
||||||
|
get => _model.TimeoutSeconds;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model.TimeoutSeconds != value)
|
||||||
|
{
|
||||||
|
_model.TimeoutSeconds = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
_onChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of concurrent search operations allowed.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxConcurrentSearches
|
||||||
|
{
|
||||||
|
get => _model.MaxConcurrentSearches;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model.MaxConcurrentSearches != value)
|
||||||
|
{
|
||||||
|
_model.MaxConcurrentSearches = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
_onChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
using JdeScoping.ConfigManager.Models;
|
||||||
|
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||||
|
|
||||||
|
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||||
|
|
||||||
|
public class SearchFormViewModelTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_InitializesFromModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new SearchSection
|
||||||
|
{
|
||||||
|
MaxResultRows = 50000,
|
||||||
|
TimeoutSeconds = 600,
|
||||||
|
MaxConcurrentSearches = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var sut = new SearchFormViewModel(model, () => { });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.MaxResultRows.ShouldBe(50000);
|
||||||
|
sut.TimeoutSeconds.ShouldBe(600);
|
||||||
|
sut.MaxConcurrentSearches.ShouldBe(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PropertyChange_UpdatesModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new SearchSection { MaxResultRows = 100000 };
|
||||||
|
var sut = new SearchFormViewModel(model, () => { });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.MaxResultRows = 25000;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
model.MaxResultRows.ShouldBe(25000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PropertyChange_InvokesOnChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new SearchSection();
|
||||||
|
var changedInvoked = false;
|
||||||
|
var sut = new SearchFormViewModel(model, () => changedInvoked = true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TimeoutSeconds = 120;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
changedInvoked.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PropertyChange_RaisesPropertyChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new SearchSection();
|
||||||
|
var sut = new SearchFormViewModel(model, () => { });
|
||||||
|
var propertyChangedRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(SearchFormViewModel.MaxConcurrentSearches))
|
||||||
|
propertyChangedRaised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.MaxConcurrentSearches = 8;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
propertyChangedRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ThrowsOnNullModel()
|
||||||
|
{
|
||||||
|
// Act & Assert
|
||||||
|
Should.Throw<ArgumentNullException>(() => new SearchFormViewModel(null!, () => { }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ThrowsOnNullCallback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new SearchSection();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Should.Throw<ArgumentNullException>(() => new SearchFormViewModel(model, null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PropertyChange_DoesNotInvokeOnChangedWhenValueUnchanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new SearchSection { MaxResultRows = 50000 };
|
||||||
|
var changedCount = 0;
|
||||||
|
var sut = new SearchFormViewModel(model, () => changedCount++);
|
||||||
|
|
||||||
|
// Act - set to same value
|
||||||
|
sut.MaxResultRows = 50000;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
changedCount.ShouldBe(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user