refactor(securestoremanager): add platform service abstractions and constants
Implement deferred code review findings: - Add IDialogService/IClipboardService interfaces for testable platform operations - Create AvaloniaDialogService and AvaloniaClipboardService implementations - Extract dialog strings and file extensions to centralized Constants classes - Refactor ViewModels to use DI instead of event delegates - Update tests to use mock services
This commit is contained in:
+6
-2
@@ -9,12 +9,16 @@ namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||
public class MainWindowViewModelTests
|
||||
{
|
||||
private readonly ISecureStoreManager _mockStoreManager;
|
||||
private readonly IDialogService _mockDialogService;
|
||||
private readonly IClipboardService _mockClipboardService;
|
||||
private readonly MainWindowViewModel _sut;
|
||||
|
||||
public MainWindowViewModelTests()
|
||||
{
|
||||
_mockStoreManager = Substitute.For<ISecureStoreManager>();
|
||||
_sut = new MainWindowViewModel(_mockStoreManager);
|
||||
_mockDialogService = Substitute.For<IDialogService>();
|
||||
_mockClipboardService = Substitute.For<IClipboardService>();
|
||||
_sut = new MainWindowViewModel(_mockStoreManager, _mockDialogService, _mockClipboardService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -280,7 +284,7 @@ public class MainWindowViewModelTests
|
||||
};
|
||||
|
||||
// Act
|
||||
_sut.SelectedSecret = new SecretItemViewModel("key", "value");
|
||||
_sut.SelectedSecret = new SecretItemViewModel("key", "value", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
|
||||
+22
-19
@@ -1,16 +1,25 @@
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.Services;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||
|
||||
public class SecretItemViewModelTests
|
||||
{
|
||||
private readonly IClipboardService _mockClipboardService;
|
||||
|
||||
public SecretItemViewModelTests()
|
||||
{
|
||||
_mockClipboardService = Substitute.For<IClipboardService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesKey()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
sut.Key.ShouldBe("testKey");
|
||||
@@ -20,7 +29,7 @@ public class SecretItemViewModelTests
|
||||
public void Constructor_InitializesActualValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
sut.ActualValue.ShouldBe("testValue");
|
||||
@@ -30,7 +39,7 @@ public class SecretItemViewModelTests
|
||||
public void Constructor_InitializesIsValueVisibleToFalse()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
@@ -40,7 +49,7 @@ public class SecretItemViewModelTests
|
||||
public void DisplayValue_WhenNotVisible_ReturnsMaskedValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Act & Assert
|
||||
sut.DisplayValue.ShouldBe("********");
|
||||
@@ -50,7 +59,7 @@ public class SecretItemViewModelTests
|
||||
public void DisplayValue_WhenVisible_ReturnsActualValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
@@ -63,7 +72,7 @@ public class SecretItemViewModelTests
|
||||
public void ToggleVisibilityCommand_TogglesIsValueVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
|
||||
// Act
|
||||
@@ -77,7 +86,7 @@ public class SecretItemViewModelTests
|
||||
public void ToggleVisibilityCommand_TogglesBackToHidden()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Act
|
||||
@@ -88,16 +97,10 @@ public class SecretItemViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CopyToClipboardCommand_RaisesOnCopyToClipboardEvent()
|
||||
public async Task CopyToClipboardCommand_CallsClipboardService()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "secretPassword");
|
||||
string? copiedValue = null;
|
||||
sut.OnCopyToClipboard += value =>
|
||||
{
|
||||
copiedValue = value;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
var sut = new SecretItemViewModel("testKey", "secretPassword", _mockClipboardService);
|
||||
|
||||
// Act
|
||||
sut.CopyToClipboardCommand.Execute(null);
|
||||
@@ -105,14 +108,14 @@ public class SecretItemViewModelTests
|
||||
// Assert - need to wait for async handler
|
||||
// Give the async void handler time to complete
|
||||
await Task.Delay(100);
|
||||
copiedValue.ShouldBe("secretPassword");
|
||||
await _mockClipboardService.Received(1).SetTextAsync("secretPassword");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_SetterRaisesPropertyChangedForIsValueVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
@@ -131,7 +134,7 @@ public class SecretItemViewModelTests
|
||||
public void IsValueVisible_SetterRaisesPropertyChangedForDisplayValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
@@ -150,7 +153,7 @@ public class SecretItemViewModelTests
|
||||
public void IsValueVisible_SetToSameValue_DoesNotRaisePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
sut.IsValueVisible = false; // Already false, but explicitly set
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
|
||||
@@ -12,14 +12,27 @@ namespace JdeScoping.SecureStoreManager.Tests.Views;
|
||||
|
||||
public class MainWindowTests
|
||||
{
|
||||
private static MainWindowViewModel CreateViewModel(ISecureStoreManager? storeManager = null)
|
||||
{
|
||||
var mockStoreManager = storeManager ?? Substitute.For<ISecureStoreManager>();
|
||||
var mockDialogService = Substitute.For<IDialogService>();
|
||||
var mockClipboardService = Substitute.For<IClipboardService>();
|
||||
return new MainWindowViewModel(mockStoreManager, mockDialogService, mockClipboardService);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_ShowsWithCorrectDefaultTitle()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
// Arrange
|
||||
var storeManager = Substitute.For<ISecureStoreManager>();
|
||||
storeManager.IsStoreOpen.Returns(false);
|
||||
var viewModel = CreateViewModel(storeManager);
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
|
||||
// Act
|
||||
window.Show();
|
||||
|
||||
// Assert
|
||||
// Assert - Title is bound to WindowTitle which is "SecureStore Manager" when no store is open
|
||||
window.Title.ShouldBe("SecureStore Manager");
|
||||
}
|
||||
|
||||
@@ -48,11 +61,14 @@ public class MainWindowTests
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_DataContextIsMainWindowViewModel()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
// Arrange
|
||||
var viewModel = CreateViewModel();
|
||||
|
||||
// Act
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
window.Show();
|
||||
|
||||
// Assert
|
||||
// Assert - DataContext is set via DI in production, but must be set manually in tests
|
||||
window.DataContext.ShouldBeOfType<MainWindowViewModel>();
|
||||
}
|
||||
|
||||
@@ -103,31 +119,36 @@ public class MainWindowTests
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_NewButtonCommand_IsBoundToViewModel()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
// Arrange
|
||||
var storeManager = Substitute.For<ISecureStoreManager>();
|
||||
var viewModel = CreateViewModel(storeManager);
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
|
||||
// Act
|
||||
window.Show();
|
||||
|
||||
var viewModel = window.DataContext as MainWindowViewModel;
|
||||
var buttons = window.GetVisualDescendants().OfType<Button>().ToList();
|
||||
var newButton = buttons.FirstOrDefault(b => b.Content?.ToString() == "New");
|
||||
|
||||
// Assert
|
||||
newButton.ShouldNotBeNull();
|
||||
newButton.Command.ShouldBe(viewModel?.NewStoreCommand);
|
||||
newButton.Command.ShouldBe(viewModel.NewStoreCommand);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_StatusBar_DisplaysStatusMessage()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
window.Show();
|
||||
// Arrange
|
||||
var storeManager = Substitute.For<ISecureStoreManager>();
|
||||
var viewModel = CreateViewModel(storeManager);
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
|
||||
var viewModel = window.DataContext as MainWindowViewModel;
|
||||
// Act
|
||||
window.Show();
|
||||
|
||||
// Assert - Find status bar text blocks
|
||||
var textBlocks = window.GetVisualDescendants().OfType<TextBlock>().ToList();
|
||||
// Status message should be "Ready" by default
|
||||
textBlocks.Any(tb => tb.Text == viewModel?.StatusMessage).ShouldBeTrue();
|
||||
textBlocks.Any(tb => tb.Text == viewModel.StatusMessage).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user