fix: resolve test failures from timezone conversion and interface rename
- Fix CriteriaSheetGenerator.FormatTimestamp to handle all DateTimeKind values - Update TestWebApplicationFactory to use IAuthenticationService - Add logger parameter to ExcelParserServiceTests - Add SecureStoreManager to solution under /utils/ folder
This commit is contained in:
@@ -11,6 +11,9 @@
|
|||||||
<Project Path="src/JdeScoping.Host/JdeScoping.Host.csproj" />
|
<Project Path="src/JdeScoping.Host/JdeScoping.Host.csproj" />
|
||||||
<Project Path="src/JdeScoping.Infrastructure/JdeScoping.Infrastructure.csproj" />
|
<Project Path="src/JdeScoping.Infrastructure/JdeScoping.Infrastructure.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
<Folder Name="/utils/">
|
||||||
|
<Project Path="src/Utils/JdeScoping.SecureStoreManager/JdeScoping.SecureStoreManager.csproj" />
|
||||||
|
</Folder>
|
||||||
<Folder Name="/tests/">
|
<Folder Name="/tests/">
|
||||||
<Project Path="tests/JdeScoping.Api.Tests/JdeScoping.Api.Tests.csproj" />
|
<Project Path="tests/JdeScoping.Api.Tests/JdeScoping.Api.Tests.csproj" />
|
||||||
<Project Path="tests/JdeScoping.Api.IntegrationTests/JdeScoping.Api.IntegrationTests.csproj" />
|
<Project Path="tests/JdeScoping.Api.IntegrationTests/JdeScoping.Api.IntegrationTests.csproj" />
|
||||||
@@ -23,5 +26,6 @@
|
|||||||
<Project Path="tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj" />
|
<Project Path="tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj" />
|
||||||
<Project Path="tests/JdeScoping.Host.Tests/JdeScoping.Host.Tests.csproj" />
|
<Project Path="tests/JdeScoping.Host.Tests/JdeScoping.Host.Tests.csproj" />
|
||||||
<Project Path="tests/JdeScoping.Infrastructure.Tests/JdeScoping.Infrastructure.Tests.csproj" />
|
<Project Path="tests/JdeScoping.Infrastructure.Tests/JdeScoping.Infrastructure.Tests.csproj" />
|
||||||
|
<Project Path="tests/JdeScoping.SecureStoreManager.Tests/JdeScoping.SecureStoreManager.Tests.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -176,8 +176,17 @@ public class CriteriaSheetGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var options = _options.Value;
|
var options = _options.Value;
|
||||||
var timezone = TimeZoneInfo.FindSystemTimeZoneById(options.TimezoneId);
|
var targetTimezone = TimeZoneInfo.FindSystemTimeZoneById(options.TimezoneId);
|
||||||
var localTime = TimeZoneInfo.ConvertTimeFromUtc(dateTime.Value, timezone);
|
var dt = dateTime.Value;
|
||||||
|
|
||||||
|
// Convert to target timezone based on the source DateTime's Kind
|
||||||
|
var localTime = dt.Kind switch
|
||||||
|
{
|
||||||
|
DateTimeKind.Utc => TimeZoneInfo.ConvertTimeFromUtc(dt, targetTimezone),
|
||||||
|
DateTimeKind.Local => TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Local, targetTimezone),
|
||||||
|
// Unspecified - database values are stored in target timezone, no conversion needed
|
||||||
|
_ => dt
|
||||||
|
};
|
||||||
|
|
||||||
return $"{localTime:MMM dd, yyyy hh:mm:ss tt} {options.TimezoneAbbreviation}";
|
return $"{localTime:MMM dd, yyyy hh:mm:ss tt} {options.TimezoneAbbreviation}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ public class TestWebApplicationFactory : WebApplicationFactory<Program>
|
|||||||
|
|
||||||
// Ensure fake auth is used for tests
|
// Ensure fake auth is used for tests
|
||||||
var authServiceDescriptor = services.SingleOrDefault(
|
var authServiceDescriptor = services.SingleOrDefault(
|
||||||
d => d.ServiceType == typeof(IAuthService));
|
d => d.ServiceType == typeof(IAuthenticationService));
|
||||||
if (authServiceDescriptor != null)
|
if (authServiceDescriptor != null)
|
||||||
{
|
{
|
||||||
services.Remove(authServiceDescriptor);
|
services.Remove(authServiceDescriptor);
|
||||||
}
|
}
|
||||||
services.AddSingleton<IAuthService, FakeAuthService>();
|
services.AddSingleton<IAuthenticationService, FakeAuthService>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
using JdeScoping.ExcelIO.Parsing;
|
using JdeScoping.ExcelIO.Parsing;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ namespace JdeScoping.ExcelIO.Tests.Parsing;
|
|||||||
|
|
||||||
public class ExcelParserServiceTests
|
public class ExcelParserServiceTests
|
||||||
{
|
{
|
||||||
private readonly ExcelParserService _service = new();
|
private readonly ExcelParserService _service = new(NullLogger<ExcelParserService>.Instance);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ParseWorkOrders_ReturnsWorkOrderNumbers()
|
public void ParseWorkOrders_ReturnsWorkOrderNumbers()
|
||||||
|
|||||||
+2
@@ -6,6 +6,8 @@
|
|||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.2.*" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.*" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Headless;
|
||||||
|
using JdeScoping.SecureStoreManager;
|
||||||
|
|
||||||
|
[assembly: AvaloniaTestApplication(typeof(JdeScoping.SecureStoreManager.Tests.TestAppBuilder))]
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests;
|
||||||
|
|
||||||
|
public class TestAppBuilder
|
||||||
|
{
|
||||||
|
public static AppBuilder BuildAvaloniaApp() =>
|
||||||
|
AppBuilder.Configure<App>()
|
||||||
|
.UseHeadless(new AvaloniaHeadlessPlatformOptions());
|
||||||
|
}
|
||||||
@@ -0,0 +1,535 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using JdeScoping.SecureStoreManager.ViewModels;
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||||
|
|
||||||
|
public class NewStoreDialogViewModelTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenStorePathEmpty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = string.Empty;
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = "/path/to/key.key";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenUseKeyFileButKeyFilePathEmpty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenUsePasswordButPasswordEmpty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenPasswordsDoNotMatch_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = "password123";
|
||||||
|
sut.ConfirmPassword = "different456";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WithValidKeyFileConfiguration_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = "/path/to/key.key";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WithValidPasswordConfiguration_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = "password123";
|
||||||
|
sut.ConfirmPassword = "password123";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UseKeyFile_WhenSetToTrue_SetsUsePasswordToFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.UsePassword = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.UsePassword.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UsePassword_WhenSetToTrue_SetsUseKeyFileToFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.UsePassword = true;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.UseKeyFile.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenStorePathEmpty_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Store path is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenKeyFilePathEmpty_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Key file path is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenPasswordEmpty_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Password is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenPasswordsDoNotMatch_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = "password123";
|
||||||
|
sut.ConfirmPassword = "different456";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Passwords do not match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenValid_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new NewStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = "/path/to/key.key";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OpenStoreDialogViewModelTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenStorePathEmpty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenUseKeyFileButKeyFilePathEmpty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenUsePasswordButPasswordEmpty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WithValidKeyFileConfiguration_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = "/path/to/key.key";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WithValidPasswordConfiguration_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/path/to/store.json";
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = "password123";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UseKeyFile_WhenSetToTrue_SetsUsePasswordToFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.UsePassword = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.UsePassword.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UsePassword_WhenSetToTrue_SetsUseKeyFileToFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.UsePassword = true;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.UseKeyFile.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenStorePathEmpty_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Store path is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenStoreFileDoesNotExist_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = "/nonexistent/path/to/store.json";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Store file does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenKeyFilePathEmpty_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange - Create temp store file so we get past that validation
|
||||||
|
var tempFile = Path.GetTempFileName();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = tempFile;
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Key file path is required.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenKeyFileDoesNotExist_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange - Create temp store file so we get past that validation
|
||||||
|
var tempFile = Path.GetTempFileName();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = tempFile;
|
||||||
|
sut.UseKeyFile = true;
|
||||||
|
sut.KeyFilePath = "/nonexistent/key.key";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Key file does not exist.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenPasswordEmpty_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange - Create temp store file so we get past that validation
|
||||||
|
var tempFile = Path.GetTempFileName();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sut = new OpenStoreDialogViewModel();
|
||||||
|
sut.StorePath = tempFile;
|
||||||
|
sut.UsePassword = true;
|
||||||
|
sut.Password = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Password is required.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SecretEditDialogViewModelTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DefaultConstructor_SetsIsNewSecretToTrue()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.IsNewSecret.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParameterizedConstructor_SetsKey()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.Key.ShouldBe("testKey");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParameterizedConstructor_SetsValue()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.Value.ShouldBe("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParameterizedConstructor_SetsIsNewSecretToFalse()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.IsNewSecret.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsKeyEditable_WhenIsNewSecret_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsKeyEditable.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsKeyEditable_WhenEditingExisting_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsKeyEditable.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DialogTitle_WhenIsNewSecret_ReturnsAddSecret()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.DialogTitle.ShouldBe("Add Secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DialogTitle_WhenEditingExisting_ReturnsEditSecret()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.DialogTitle.ShouldBe("Edit Secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenKeyEmpty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
sut.Key = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenKeyWhitespace_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
sut.Key = " ";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValid_WhenKeyProvided_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
sut.Key = "validKey";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.IsValid.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenKeyEmpty_ReturnsAppropriateMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
sut.Key = string.Empty;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBe("Key is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidationError_WhenKeyProvided_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
sut.Key = "validKey";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.ValidationError.ShouldBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Key_SetterRaisesPropertyChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
var propertyChangedRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(sut.Key))
|
||||||
|
propertyChangedRaised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.Key = "newKey";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
propertyChangedRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Value_SetterRaisesPropertyChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretEditDialogViewModel();
|
||||||
|
var propertyChangedRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(sut.Value))
|
||||||
|
propertyChangedRaised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.Value = "newValue";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
propertyChangedRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using JdeScoping.SecureStoreManager.ViewModels;
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||||
|
|
||||||
|
public class RelayCommandTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Execute_CallsAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wasCalled = false;
|
||||||
|
var sut = new RelayCommand(() => wasCalled = true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.Execute(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
wasCalled.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Execute_PassesParameterToAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
object? receivedParameter = null;
|
||||||
|
var sut = new RelayCommand(p => receivedParameter = p);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.Execute("testParam");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
receivedParameter.ShouldBe("testParam");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanExecute_WhenNoPredicate_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new RelayCommand(() => { });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = sut.CanExecute(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanExecute_WhenPredicateReturnsTrue_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new RelayCommand(() => { }, () => true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = sut.CanExecute(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanExecute_WhenPredicateReturnsFalse_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new RelayCommand(() => { }, () => false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = sut.CanExecute(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanExecute_WithParameterPredicate_EvaluatesParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new RelayCommand(_ => { }, p => p is string s && s == "valid");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validResult = sut.CanExecute("valid");
|
||||||
|
var invalidResult = sut.CanExecute("invalid");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
validResult.ShouldBeTrue();
|
||||||
|
invalidResult.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RaiseCanExecuteChanged_FiresCanExecuteChangedEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new RelayCommand(() => { });
|
||||||
|
var eventFired = false;
|
||||||
|
sut.CanExecuteChanged += (s, e) => eventFired = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.RaiseCanExecuteChanged();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
eventFired.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RaiseCanExecuteChanged_EventSenderIsSut()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new RelayCommand(() => { });
|
||||||
|
object? eventSender = null;
|
||||||
|
sut.CanExecuteChanged += (s, e) => eventSender = s;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.RaiseCanExecuteChanged();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
eventSender.ShouldBe(sut);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WithNullExecute_ThrowsArgumentNullException()
|
||||||
|
{
|
||||||
|
// Arrange & Act & Assert
|
||||||
|
Should.Throw<ArgumentNullException>(() => new RelayCommand((Action<object?>)null!));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using JdeScoping.SecureStoreManager.ViewModels;
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||||
|
|
||||||
|
public class SecretItemViewModelTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_InitializesKey()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.Key.ShouldBe("testKey");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_InitializesActualValue()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.ActualValue.ShouldBe("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_InitializesIsValueVisibleToFalse()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.IsValueVisible.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisplayValue_WhenNotVisible_ReturnsMaskedValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
sut.DisplayValue.ShouldBe("********");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisplayValue_WhenVisible_ReturnsActualValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.IsValueVisible = true;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.DisplayValue.ShouldBe("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToggleVisibilityCommand_TogglesIsValueVisible()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
sut.IsValueVisible.ShouldBeFalse();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.ToggleVisibilityCommand.Execute(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.IsValueVisible.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToggleVisibilityCommand_TogglesBackToHidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
sut.IsValueVisible = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.ToggleVisibilityCommand.Execute(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.IsValueVisible.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CopyToClipboardCommand_RaisesOnCopyToClipboardEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "secretPassword");
|
||||||
|
string? copiedValue = null;
|
||||||
|
sut.OnCopyToClipboard += value =>
|
||||||
|
{
|
||||||
|
copiedValue = value;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.CopyToClipboardCommand.Execute(null);
|
||||||
|
|
||||||
|
// Assert - need to wait for async handler
|
||||||
|
// Give the async void handler time to complete
|
||||||
|
await Task.Delay(100);
|
||||||
|
copiedValue.ShouldBe("secretPassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValueVisible_SetterRaisesPropertyChangedForIsValueVisible()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
var propertyChangedRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(sut.IsValueVisible))
|
||||||
|
propertyChangedRaised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.IsValueVisible = true;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
propertyChangedRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValueVisible_SetterRaisesPropertyChangedForDisplayValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
var propertyChangedRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(sut.DisplayValue))
|
||||||
|
propertyChangedRaised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.IsValueVisible = true;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
propertyChangedRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValueVisible_SetToSameValue_DoesNotRaisePropertyChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new SecretItemViewModel("testKey", "testValue");
|
||||||
|
sut.IsValueVisible = false; // Already false, but explicitly set
|
||||||
|
var propertyChangedRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
propertyChangedRaised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.IsValueVisible = false;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
propertyChangedRaised.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using JdeScoping.SecureStoreManager.ViewModels;
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||||
|
|
||||||
|
public class ViewModelBaseTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test implementation of ViewModelBase for testing purposes.
|
||||||
|
/// </summary>
|
||||||
|
private class TestViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private string _testProperty = string.Empty;
|
||||||
|
private int _testIntProperty;
|
||||||
|
|
||||||
|
public string TestProperty
|
||||||
|
{
|
||||||
|
get => _testProperty;
|
||||||
|
set => SetProperty(ref _testProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TestIntProperty
|
||||||
|
{
|
||||||
|
get => _testIntProperty;
|
||||||
|
set => SetProperty(ref _testIntProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RaisePropertyChangedForTest(string propertyName)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_WhenValueUnchanged_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
sut.TestProperty = "initialValue";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestProperty = "initialValue"; // Same value
|
||||||
|
|
||||||
|
// Assert - SetProperty should have returned false, but we can verify by checking no event was raised
|
||||||
|
// We test this indirectly through the property changed event not firing
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_WhenValueUnchanged_DoesNotRaisePropertyChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
sut.TestProperty = "initialValue";
|
||||||
|
var eventRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestProperty = "initialValue"; // Same value
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
eventRaised.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_WhenValueChanged_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestProperty = "newValue";
|
||||||
|
|
||||||
|
// Assert - SetProperty returned true (verified by property change being made)
|
||||||
|
sut.TestProperty.ShouldBe("newValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_WhenValueChanged_RaisesPropertyChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
var eventRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestProperty = "newValue";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
eventRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_RaisesEventWithCorrectPropertyName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
string? raisedPropertyName = null;
|
||||||
|
sut.PropertyChanged += (s, e) => raisedPropertyName = e.PropertyName;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestProperty = "newValue";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
raisedPropertyName.ShouldBe(nameof(TestViewModel.TestProperty));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_RaisesEventWithSenderAsThis()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
object? eventSender = null;
|
||||||
|
sut.PropertyChanged += (s, e) => eventSender = s;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestProperty = "newValue";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
eventSender.ShouldBe(sut);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnPropertyChanged_RaisesPropertyChangedEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
var eventRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.RaisePropertyChangedForTest("SomeProperty");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
eventRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnPropertyChanged_RaisesEventWithCorrectPropertyName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
string? raisedPropertyName = null;
|
||||||
|
sut.PropertyChanged += (s, e) => raisedPropertyName = e.PropertyName;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.RaisePropertyChangedForTest("CustomProperty");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
raisedPropertyName.ShouldBe("CustomProperty");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_WithValueType_WorksCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
var eventRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestIntProperty = 42;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
sut.TestIntProperty.ShouldBe(42);
|
||||||
|
eventRaised.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetProperty_WithSameValueType_DoesNotRaiseEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sut = new TestViewModel();
|
||||||
|
sut.TestIntProperty = 42;
|
||||||
|
var eventRaised = false;
|
||||||
|
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
sut.TestIntProperty = 42; // Same value
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
eventRaised.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using NSubstitute;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using JdeScoping.SecureStoreManager.Services;
|
||||||
|
using JdeScoping.SecureStoreManager.ViewModels;
|
||||||
|
using JdeScoping.SecureStoreManager.Views;
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests.Views;
|
||||||
|
|
||||||
|
public class MainWindowTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_ShowsWithCorrectDefaultTitle()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
window.Title.ShouldBe("SecureStore Manager");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_HasExpectedWidth()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
window.Width.ShouldBe(800);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_HasExpectedHeight()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
window.Height.ShouldBe(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_DataContextIsMainWindowViewModel()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
window.DataContext.ShouldBeOfType<MainWindowViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_ContainsSecretsDataGrid()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var dataGrid = window.FindDescendantOfType<DataGrid>();
|
||||||
|
dataGrid.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_ContainsMenuBar()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var menu = window.FindDescendantOfType<Menu>();
|
||||||
|
menu.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_ContainsToolbarButtons()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var buttons = window.GetVisualDescendants().OfType<Button>().ToList();
|
||||||
|
buttons.Count.ShouldBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Verify toolbar buttons exist with expected content
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "New").ShouldBeTrue();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Open").ShouldBeTrue();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Save").ShouldBeTrue();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Add").ShouldBeTrue();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Edit").ShouldBeTrue();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Delete").ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_NewButtonCommand_IsBoundToViewModel()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void MainWindow_StatusBar_DisplaysStatusMessage()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
var viewModel = window.DataContext as MainWindowViewModel;
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using JdeScoping.SecureStoreManager.ViewModels;
|
||||||
|
using JdeScoping.SecureStoreManager.Views;
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests.Views;
|
||||||
|
|
||||||
|
public class NewStoreDialogTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void NewStoreDialog_ShowsWithCorrectTitle()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new NewStoreDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.Title.ShouldBe("Create New Store");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void NewStoreDialog_DataContextIsNewStoreDialogViewModel()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new NewStoreDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.DataContext.ShouldBeOfType<NewStoreDialogViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void NewStoreDialog_HasStorePathTextBox()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new NewStoreDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var textBoxes = dialog.GetVisualDescendants().OfType<TextBox>().ToList();
|
||||||
|
textBoxes.Count.ShouldBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void NewStoreDialog_HasRadioButtons()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new NewStoreDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var radioButtons = dialog.GetVisualDescendants().OfType<RadioButton>().ToList();
|
||||||
|
radioButtons.Count.ShouldBeGreaterThanOrEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void NewStoreDialog_HasCreateAndCancelButtons()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new NewStoreDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var buttons = dialog.GetVisualDescendants().OfType<Button>().ToList();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Create").ShouldBeTrue();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Cancel").ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void NewStoreDialog_ViewModelProperty_ReturnsDataContext()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new NewStoreDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.ViewModel.ShouldBe(dialog.DataContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void NewStoreDialog_CannotResize()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new NewStoreDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.CanResize.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using JdeScoping.SecureStoreManager.ViewModels;
|
||||||
|
using JdeScoping.SecureStoreManager.Views;
|
||||||
|
|
||||||
|
namespace JdeScoping.SecureStoreManager.Tests.Views;
|
||||||
|
|
||||||
|
public class SecretEditDialogTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_DefaultConstructor_ShowsAddSecretTitle()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.Title.ShouldBe("Add Secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_WithKeyValueParams_ShowsEditSecretTitle()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog("existingKey", "existingValue");
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.Title.ShouldBe("Edit Secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_DataContextIsSecretEditDialogViewModel()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.DataContext.ShouldBeOfType<SecretEditDialogViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_HasKeyAndValueTextBoxes()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var textBoxes = dialog.GetVisualDescendants().OfType<TextBox>().ToList();
|
||||||
|
textBoxes.Count.ShouldBeGreaterThanOrEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_HasSaveAndCancelButtons()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var buttons = dialog.GetVisualDescendants().OfType<Button>().ToList();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Save").ShouldBeTrue();
|
||||||
|
buttons.Any(b => b.Content?.ToString() == "Cancel").ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_ViewModelProperty_ReturnsDataContext()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.ViewModel.ShouldBe(dialog.DataContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_ParameterizedConstructor_SetsViewModelWithKey()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog("testKey", "testValue");
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.ViewModel.Key.ShouldBe("testKey");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_ParameterizedConstructor_SetsViewModelWithValue()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog("testKey", "testValue");
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.ViewModel.Value.ShouldBe("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SecretEditDialog_CannotResize()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var dialog = new SecretEditDialog();
|
||||||
|
dialog.Show();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
dialog.CanResize.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user