Files
hiha-arvio/tests/HihaArvio.Tests/ViewModels/SettingsViewModelTests.cs
Ismo Vuorinen 48a844b0c1 feat: implement ViewModels layer with TDD (Milestone 3)
Implemented all ViewModels using strict TDD (RED-GREEN-REFACTOR) with CommunityToolkit.Mvvm.

**ViewModels Implemented:**
- MainViewModel: Coordinates shake detection, estimate generation, and display
- HistoryViewModel: Manages estimate history display and clearing
- SettingsViewModel: Handles app settings (mode selection, history size)

**MainViewModel Features:**
- Subscribes to ShakeDetectionService events
- Generates estimates when shake stops (transition from shaking→not shaking)
- Automatically saves estimates to storage
- Resets shake detection after estimate generation
- Loads and saves settings (SelectedMode)
- Proper disposal pattern (unsubscribe, stop monitoring)

**HistoryViewModel Features:**
- ObservableCollection<EstimateResult> for UI binding
- LoadHistoryCommand to fetch from storage
- ClearHistoryCommand to remove all history
- IsEmpty property for conditional UI display
- Replaces collection contents on reload

**SettingsViewModel Features:**
- SelectedMode property (Work/Generic)
- MaxHistorySize property
- SaveSettingsCommand to persist changes
- Loads settings on initialization

**Tests:**
- MainViewModel: 18 tests (RED-GREEN-REFACTOR)
- HistoryViewModel: 15 tests (RED-GREEN-REFACTOR)
- SettingsViewModel: 13 tests (RED-GREEN-REFACTOR)
- Total: 165 tests, all passing (48 models + 71 services + 46 ViewModels)

**Quality:**
- Build: 0 warnings, 0 errors across all platforms
- All tests use NSubstitute for mocking
- Property change notifications verified
- Async operations properly tested

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 12:44:07 +02:00

223 lines
5.9 KiB
C#

using FluentAssertions;
using HihaArvio.Models;
using HihaArvio.Services.Interfaces;
using HihaArvio.ViewModels;
using NSubstitute;
namespace HihaArvio.Tests.ViewModels;
public class SettingsViewModelTests
{
private readonly IStorageService _storageService;
private readonly SettingsViewModel _viewModel;
public SettingsViewModelTests()
{
_storageService = Substitute.For<IStorageService>();
// Setup default storage service behavior
_storageService.LoadSettingsAsync().Returns(Task.FromResult(new AppSettings
{
SelectedMode = EstimateMode.Work,
MaxHistorySize = 10
}));
_viewModel = new SettingsViewModel(_storageService);
}
#region Initialization Tests
[Fact]
public async Task Constructor_ShouldLoadSettings()
{
// Arrange - Create new instance to test initialization
var storageService = Substitute.For<IStorageService>();
var settings = new AppSettings { SelectedMode = EstimateMode.Generic, MaxHistorySize = 20 };
storageService.LoadSettingsAsync().Returns(Task.FromResult(settings));
// Act
var vm = new SettingsViewModel(storageService);
await Task.Delay(50); // Give async initialization time to complete
// Assert
vm.SelectedMode.Should().Be(EstimateMode.Generic);
vm.MaxHistorySize.Should().Be(20);
await storageService.Received(1).LoadSettingsAsync();
}
[Fact]
public void Constructor_ShouldHaveSaveSettingsCommand()
{
// Assert
_viewModel.SaveSettingsCommand.Should().NotBeNull();
}
#endregion
#region Save Settings Tests
[Fact]
public async Task SaveSettingsCommand_ShouldSaveToStorage()
{
// Act
await _viewModel.SaveSettingsCommand.ExecuteAsync(null);
// Assert
await _storageService.Received(1).SaveSettingsAsync(Arg.Any<AppSettings>());
}
[Fact]
public async Task SaveSettingsCommand_ShouldSaveCurrentSettings()
{
// Arrange
_viewModel.SelectedMode = EstimateMode.Generic;
_viewModel.MaxHistorySize = 25;
// Act
await _viewModel.SaveSettingsCommand.ExecuteAsync(null);
// Assert
await _storageService.Received(1).SaveSettingsAsync(Arg.Is<AppSettings>(s =>
s.SelectedMode == EstimateMode.Generic &&
s.MaxHistorySize == 25));
}
#endregion
#region Selected Mode Tests
[Fact]
public void SelectedMode_WhenChanged_ShouldUpdateProperty()
{
// Act
_viewModel.SelectedMode = EstimateMode.Generic;
// Assert
_viewModel.SelectedMode.Should().Be(EstimateMode.Generic);
}
[Fact]
public void SelectedMode_ShouldSupportWorkMode()
{
// Act
_viewModel.SelectedMode = EstimateMode.Work;
// Assert
_viewModel.SelectedMode.Should().Be(EstimateMode.Work);
}
[Fact]
public void SelectedMode_ShouldSupportGenericMode()
{
// Act
_viewModel.SelectedMode = EstimateMode.Generic;
// Assert
_viewModel.SelectedMode.Should().Be(EstimateMode.Generic);
}
// Note: Humorous mode is only triggered by easter egg, not selectable in UI
#endregion
#region Max History Size Tests
[Fact]
public void MaxHistorySize_WhenChanged_ShouldUpdateProperty()
{
// Act
_viewModel.MaxHistorySize = 50;
// Assert
_viewModel.MaxHistorySize.Should().Be(50);
}
[Fact]
public void MaxHistorySize_ShouldAcceptValidValues()
{
// Act & Assert
var testAction = () => _viewModel.MaxHistorySize = 100;
testAction.Should().NotThrow();
_viewModel.MaxHistorySize.Should().Be(100);
}
#endregion
#region Property Change Notification Tests
[Fact]
public void SelectedMode_WhenChanged_ShouldRaisePropertyChanged()
{
// Arrange
var propertyChangedRaised = false;
_viewModel.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(SettingsViewModel.SelectedMode))
propertyChangedRaised = true;
};
// Act
_viewModel.SelectedMode = EstimateMode.Generic;
// Assert
propertyChangedRaised.Should().BeTrue();
}
[Fact]
public void MaxHistorySize_WhenChanged_ShouldRaisePropertyChanged()
{
// Arrange
var propertyChangedRaised = false;
_viewModel.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(SettingsViewModel.MaxHistorySize))
propertyChangedRaised = true;
};
// Act
_viewModel.MaxHistorySize = 50;
// Assert
propertyChangedRaised.Should().BeTrue();
}
#endregion
#region Integration Tests
[Fact]
public async Task ChangeSettings_ThenSave_ShouldPersistChanges()
{
// Arrange
_viewModel.SelectedMode = EstimateMode.Generic;
_viewModel.MaxHistorySize = 30;
// Act
await _viewModel.SaveSettingsCommand.ExecuteAsync(null);
// Assert
await _storageService.Received(1).SaveSettingsAsync(Arg.Is<AppSettings>(s =>
s.SelectedMode == EstimateMode.Generic &&
s.MaxHistorySize == 30));
}
[Fact]
public async Task LoadSettings_ShouldPopulateProperties()
{
// Arrange - Create new instance with specific settings
var storageService = Substitute.For<IStorageService>();
var settings = new AppSettings { SelectedMode = EstimateMode.Generic, MaxHistorySize = 50 };
storageService.LoadSettingsAsync().Returns(Task.FromResult(settings));
// Act
var vm = new SettingsViewModel(storageService);
await Task.Delay(50); // Give async load time to complete
// Assert
vm.SelectedMode.Should().Be(EstimateMode.Generic);
vm.MaxHistorySize.Should().Be(50);
}
#endregion
}