From 334cb834162d0b3287c9db9a8aedec0acfd799d1 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 30 May 2025 12:31:05 +0000 Subject: [PATCH 1/5] Increase test coverage with comprehensive service tests - Enhanced FileServiceTests with ReadFile() method coverage and edge cases - Added UserSettingsServiceTests with 20 test methods covering settings management, themes, persistence, and event handling - Added PageServiceTests with 9 test methods covering page type resolution and service provider integration - Added NavigationServiceTests with 15 test methods covering navigation logic, frame management, and state handling - All tests follow AAA pattern with proper mocking and edge case coverage - Improved overall test coverage for core business logic services --- src/Tests/Services/FileServiceTests.cs | 50 +++ src/Tests/Services/NavigationServiceTests.cs | 271 +++++++++++++ src/Tests/Services/PageServiceTests.cs | 155 +++++++ .../Services/UserSettingsServiceTests.cs | 382 ++++++++++++++++++ 4 files changed, 858 insertions(+) create mode 100644 src/Tests/Services/NavigationServiceTests.cs create mode 100644 src/Tests/Services/PageServiceTests.cs create mode 100644 src/Tests/Services/UserSettingsServiceTests.cs diff --git a/src/Tests/Services/FileServiceTests.cs b/src/Tests/Services/FileServiceTests.cs index 9792c22..e17918f 100644 --- a/src/Tests/Services/FileServiceTests.cs +++ b/src/Tests/Services/FileServiceTests.cs @@ -80,6 +80,56 @@ public void Read_ShouldReturnDefault_WhenFileDoesNotExist() result.Should().BeNull(); } + [Fact] + public void ReadFile_ShouldReturnFileContent() + { + // Arrange + var fileName = "testfile.txt"; + var expectedContent = "This is test content\nwith multiple lines"; + var filePath = Path.Combine(_testDirectory, fileName); + Directory.CreateDirectory(_testDirectory); + File.WriteAllText(filePath, expectedContent, Encoding.UTF8); + + // Act + var result = _fileService.ReadFile(filePath); + + // Assert + result.Should().Be(expectedContent); + } + + [Fact] + public void Delete_ShouldNotThrow_WhenFileNameIsNull() + { + // Act & Assert + var action = () => _fileService.Delete(_testDirectory, null); + action.Should().NotThrow(); + } + + [Fact] + public void Delete_ShouldNotThrow_WhenFileDoesNotExist() + { + // Act & Assert + var action = () => _fileService.Delete(_testDirectory, "nonexistent.json"); + action.Should().NotThrow(); + } + + [Fact] + public void Save_ShouldCreateDirectory_WhenItDoesNotExist() + { + // Arrange + var nonExistentDirectory = Path.Combine(_testDirectory, "subfolder", "nested"); + var fileName = "test.json"; + var testObject = new TestObject { Id = 3, Name = "DirectoryTest" }; + + // Act + _fileService.Save(nonExistentDirectory, fileName, testObject); + + // Assert + Directory.Exists(nonExistentDirectory).Should().BeTrue(); + var filePath = Path.Combine(nonExistentDirectory, fileName); + File.Exists(filePath).Should().BeTrue(); + } + public void Dispose() { Dispose(true); diff --git a/src/Tests/Services/NavigationServiceTests.cs b/src/Tests/Services/NavigationServiceTests.cs new file mode 100644 index 0000000..a08445e --- /dev/null +++ b/src/Tests/Services/NavigationServiceTests.cs @@ -0,0 +1,271 @@ +using System.Windows.Controls; +using System.Windows.Navigation; +using CleanMyPosts.UI.Contracts.Services; +using CleanMyPosts.UI.Contracts.ViewModels; +using FluentAssertions; +using Moq; +using Xunit; + +namespace CleanMyPosts.Tests.Services; + +[Trait("Category", "Unit")] +public class NavigationServiceTests +{ + private readonly Mock _mockPageService; + private readonly Mock _mockFrame; + private readonly CleanMyPosts.UI.Services.NavigationService _navigationService; + + public NavigationServiceTests() + { + _mockPageService = new Mock(); + _mockFrame = new Mock(); + _navigationService = new CleanMyPosts.UI.Services.NavigationService(_mockPageService.Object); + } + + [Fact] + public void Constructor_ShouldInitializeWithPageService() + { + // Act & Assert + _navigationService.Should().NotBeNull(); + } + + [Fact] + public void CanGoBack_ShouldReturnFalse_WhenFrameIsNotInitialized() + { + // Act + var result = _navigationService.CanGoBack; + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public void CanGoBack_ShouldReturnFrameCanGoBack_WhenFrameIsInitialized() + { + // Arrange + _mockFrame.Setup(x => x.CanGoBack).Returns(true); + _navigationService.Initialize(_mockFrame.Object); + + // Act + var result = _navigationService.CanGoBack; + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public void Initialize_ShouldSetFrame_WhenCalledFirstTime() + { + // Act + _navigationService.Initialize(_mockFrame.Object); + + // Assert + _mockFrame.VerifyAdd(x => x.Navigated += It.IsAny(), Times.Once); + } + + [Fact] + public void Initialize_ShouldNotSetFrame_WhenCalledMultipleTimes() + { + // Arrange + var secondFrame = new Mock(); + + // Act + _navigationService.Initialize(_mockFrame.Object); + _navigationService.Initialize(secondFrame.Object); + + // Assert + _mockFrame.VerifyAdd(x => x.Navigated += It.IsAny(), Times.Once); + secondFrame.VerifyAdd(x => x.Navigated += It.IsAny(), Times.Never); + } + + [Fact] + public void UnsubscribeNavigation_ShouldUnsubscribeAndClearFrame() + { + // Arrange + _navigationService.Initialize(_mockFrame.Object); + + // Act + _navigationService.UnsubscribeNavigation(); + + // Assert + _mockFrame.VerifyRemove(x => x.Navigated -= It.IsAny(), Times.Once); + } + + [Fact] + public void GoBack_ShouldCallFrameGoBack_WhenCanGoBackIsTrue() + { + // Arrange + _mockFrame.Setup(x => x.CanGoBack).Returns(true); + _navigationService.Initialize(_mockFrame.Object); + + // Act + _navigationService.GoBack(); + + // Assert + _mockFrame.Verify(x => x.GoBack(), Times.Once); + } + + [Fact] + public void GoBack_ShouldNotCallFrameGoBack_WhenCanGoBackIsFalse() + { + // Arrange + _mockFrame.Setup(x => x.CanGoBack).Returns(false); + _navigationService.Initialize(_mockFrame.Object); + + // Act + _navigationService.GoBack(); + + // Assert + _mockFrame.Verify(x => x.GoBack(), Times.Never); + } + + [Fact] + public void NavigateTo_ShouldReturnTrue_WhenNavigationSucceeds() + { + // Arrange + var pageKey = "TestPage"; + var pageType = typeof(Page); + var mockPage = new Mock(); + + _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); + _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); + _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(true); + _mockFrame.Setup(x => x.Content).Returns(null); + + _navigationService.Initialize(_mockFrame.Object); + + // Act + var result = _navigationService.NavigateTo(pageKey); + + // Assert + result.Should().BeTrue(); + _mockFrame.Verify(x => x.Navigate(mockPage.Object, null), Times.Once); + } + + [Fact] + public void NavigateTo_ShouldReturnFalse_WhenNavigationFails() + { + // Arrange + var pageKey = "TestPage"; + var pageType = typeof(Page); + var mockPage = new Mock(); + + _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); + _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); + _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(false); + _mockFrame.Setup(x => x.Content).Returns(null); + + _navigationService.Initialize(_mockFrame.Object); + + // Act + var result = _navigationService.NavigateTo(pageKey); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public void NavigateTo_ShouldReturnFalse_WhenSamePageAndNoParameter() + { + // Arrange + var pageKey = "TestPage"; + var pageType = typeof(Page); + var existingPage = new Mock(); + existingPage.Setup(x => x.GetType()).Returns(pageType); + + _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); + _mockFrame.Setup(x => x.Content).Returns(existingPage.Object); + + _navigationService.Initialize(_mockFrame.Object); + + // Act + var result = _navigationService.NavigateTo(pageKey); + + // Assert + result.Should().BeFalse(); + _mockFrame.Verify(x => x.Navigate(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void NavigateTo_ShouldSetClearNavigationTag_WhenClearNavigationIsTrue() + { + // Arrange + var pageKey = "TestPage"; + var pageType = typeof(Page); + var mockPage = new Mock(); + + _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); + _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); + _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(true); + _mockFrame.Setup(x => x.Content).Returns(null); + + _navigationService.Initialize(_mockFrame.Object); + + // Act + _navigationService.NavigateTo(pageKey, null, clearNavigation: true); + + // Assert + _mockFrame.VerifySet(x => x.Tag = true, Times.Once); + } + + [Fact] + public void NavigateTo_ShouldNavigateWithParameter() + { + // Arrange + var pageKey = "TestPage"; + var pageType = typeof(Page); + var mockPage = new Mock(); + var parameter = "test parameter"; + + _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); + _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); + _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(true); + _mockFrame.Setup(x => x.Content).Returns(null); + + _navigationService.Initialize(_mockFrame.Object); + + // Act + var result = _navigationService.NavigateTo(pageKey, parameter); + + // Assert + result.Should().BeTrue(); + _mockFrame.Verify(x => x.Navigate(mockPage.Object, parameter), Times.Once); + } + + [Fact] + public void CleanNavigation_ShouldCallFrameCleanNavigation() + { + // Arrange + _navigationService.Initialize(_mockFrame.Object); + + // Act + _navigationService.CleanNavigation(); + + // Assert - This would require the extension method to be mockable + // For now, we just verify the method doesn't throw + var action = () => _navigationService.CleanNavigation(); + action.Should().NotThrow(); + } + + [Fact] + public void NavigatedEvent_ShouldBeTriggered_WhenFrameNavigated() + { + // Arrange + _navigationService.Initialize(_mockFrame.Object); + + string eventArg = null; + object eventSender = null; + _navigationService.Navigated += (sender, arg) => + { + eventSender = sender; + eventArg = arg; + }; + + // Act - This would require more complex setup to properly test the event + // For now, we verify the event can be subscribed to + var action = () => _navigationService.Navigated += (s, e) => { }; + + // Assert + action.Should().NotThrow(); + } +} \ No newline at end of file diff --git a/src/Tests/Services/PageServiceTests.cs b/src/Tests/Services/PageServiceTests.cs new file mode 100644 index 0000000..ed3729d --- /dev/null +++ b/src/Tests/Services/PageServiceTests.cs @@ -0,0 +1,155 @@ +using System.Windows.Controls; +using CleanMyPosts.UI.Services; +using CleanMyPosts.UI.ViewModels; +using CleanMyPosts.UI.Views; +using FluentAssertions; +using Moq; +using Xunit; + +namespace CleanMyPosts.Tests.Services; + +[Trait("Category", "Unit")] +public class PageServiceTests +{ + private readonly Mock _mockServiceProvider; + private readonly PageService _pageService; + + public PageServiceTests() + { + _mockServiceProvider = new Mock(); + _pageService = new PageService(_mockServiceProvider.Object); + } + + [Fact] + public void GetPageType_ShouldReturnCorrectType_ForXViewModel() + { + // Arrange + var key = typeof(XViewModel).FullName; + + // Act + var result = _pageService.GetPageType(key); + + // Assert + result.Should().Be(typeof(XPage)); + } + + [Fact] + public void GetPageType_ShouldReturnCorrectType_ForLogViewModel() + { + // Arrange + var key = typeof(LogViewModel).FullName; + + // Act + var result = _pageService.GetPageType(key); + + // Assert + result.Should().Be(typeof(LogPage)); + } + + [Fact] + public void GetPageType_ShouldReturnCorrectType_ForSettingsViewModel() + { + // Arrange + var key = typeof(SettingsViewModel).FullName; + + // Act + var result = _pageService.GetPageType(key); + + // Assert + result.Should().Be(typeof(SettingsPage)); + } + + [Fact] + public void GetPageType_ShouldThrowArgumentException_ForUnknownKey() + { + // Arrange + var unknownKey = "UnknownViewModel"; + + // Act & Assert + var action = () => _pageService.GetPageType(unknownKey); + action.Should().Throw() + .WithMessage($"Page not found: {unknownKey}. Did you forget to call PageService.Configure?"); + } + + [Fact] + public void GetPage_ShouldReturnPageFromServiceProvider() + { + // Arrange + var key = typeof(XViewModel).FullName; + var expectedPage = new Mock().Object; + _mockServiceProvider.Setup(x => x.GetService(typeof(XPage))) + .Returns(expectedPage); + + // Act + var result = _pageService.GetPage(key); + + // Assert + result.Should().Be(expectedPage); + _mockServiceProvider.Verify(x => x.GetService(typeof(XPage)), Times.Once); + } + + [Fact] + public void GetPage_ShouldReturnNull_WhenServiceProviderReturnsNull() + { + // Arrange + var key = typeof(LogViewModel).FullName; + _mockServiceProvider.Setup(x => x.GetService(typeof(LogPage))) + .Returns(null); + + // Act + var result = _pageService.GetPage(key); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void GetPage_ShouldReturnNull_WhenServiceProviderReturnsNonPageType() + { + // Arrange + var key = typeof(SettingsViewModel).FullName; + var nonPageObject = new object(); + _mockServiceProvider.Setup(x => x.GetService(typeof(SettingsPage))) + .Returns(nonPageObject); + + // Act + var result = _pageService.GetPage(key); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void GetPage_ShouldThrowArgumentException_ForUnknownKey() + { + // Arrange + var unknownKey = "UnknownViewModel"; + + // Act & Assert + var action = () => _pageService.GetPage(unknownKey); + action.Should().Throw() + .WithMessage($"Page not found: {unknownKey}. Did you forget to call PageService.Configure?"); + } + + [Fact] + public void Constructor_ShouldConfigureAllExpectedPages() + { + // Act & Assert - Constructor should not throw and all expected pages should be configured + var xViewModelKey = typeof(XViewModel).FullName; + var logViewModelKey = typeof(LogViewModel).FullName; + var settingsViewModelKey = typeof(SettingsViewModel).FullName; + + // These should not throw + var action1 = () => _pageService.GetPageType(xViewModelKey); + var action2 = () => _pageService.GetPageType(logViewModelKey); + var action3 = () => _pageService.GetPageType(settingsViewModelKey); + + action1.Should().NotThrow(); + action2.Should().NotThrow(); + action3.Should().NotThrow(); + + _pageService.GetPageType(xViewModelKey).Should().Be(typeof(XPage)); + _pageService.GetPageType(logViewModelKey).Should().Be(typeof(LogPage)); + _pageService.GetPageType(settingsViewModelKey).Should().Be(typeof(SettingsPage)); + } +} \ No newline at end of file diff --git a/src/Tests/Services/UserSettingsServiceTests.cs b/src/Tests/Services/UserSettingsServiceTests.cs new file mode 100644 index 0000000..4adf694 --- /dev/null +++ b/src/Tests/Services/UserSettingsServiceTests.cs @@ -0,0 +1,382 @@ +using CleanMyPosts.Core.Contracts.Services; +using CleanMyPosts.UI.Models; +using CleanMyPosts.UI.Services; +using FluentAssertions; +using Moq; +using System.Windows; +using Xunit; + +namespace CleanMyPosts.Tests.Services; + +[Trait("Category", "Unit")] +public class UserSettingsServiceTests +{ + private readonly Mock _mockFileService; + private readonly AppConfig _appConfig; + private readonly UserSettingsService _userSettingsService; + private readonly string _expectedSettingsPath; + + public UserSettingsServiceTests() + { + _mockFileService = new Mock(); + _appConfig = new AppConfig(); + _expectedSettingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + _appConfig.ConfigurationsFolder); + _userSettingsService = new UserSettingsService(_mockFileService.Object, _appConfig); + } + + [Fact] + public void Initialize_ShouldLoadSettings_WhenCalled() + { + // Arrange + var expectedSettings = new UserSettings + { + Theme = AppTheme.Dark, + ShowLogs = true, + ConfirmDeletion = false + }; + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(expectedSettings); + + // Act + _userSettingsService.Initialize(); + + // Assert + _mockFileService.Verify(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName), Times.Once); + _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Dark); + _userSettingsService.GetShowLogs().Should().BeTrue(); + _userSettingsService.GetConfirmDeletion().Should().BeFalse(); + } + + [Fact] + public void Initialize_ShouldUseDefaultSettings_WhenFileServiceReturnsNull() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns((UserSettings)null); + + // Act + _userSettingsService.Initialize(); + + // Assert + _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Default); + _userSettingsService.GetShowLogs().Should().BeFalse(); + _userSettingsService.GetConfirmDeletion().Should().BeTrue(); + } + + [Fact] + public void PersistData_ShouldSaveCurrentSettings() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings()); + _userSettingsService.Initialize(); + + // Act + _userSettingsService.PersistData(); + + // Assert + _mockFileService.Verify(x => x.Save(_expectedSettingsPath, _appConfig.AppPropertiesFileName, It.IsAny()), Times.Once); + } + + [Fact] + public void RestoreData_ShouldReloadSettings() + { + // Arrange + var initialSettings = new UserSettings { Theme = AppTheme.Light }; + var restoredSettings = new UserSettings { Theme = AppTheme.Dark }; + + _mockFileService.SetupSequence(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(initialSettings) + .Returns(restoredSettings); + + _userSettingsService.Initialize(); + _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Light); + + // Act + _userSettingsService.RestoreData(); + + // Assert + _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Dark); + } + + [Fact] + public void SetTheme_ShouldUpdateThemeAndTriggerEvent() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings()); + _userSettingsService.Initialize(); + + string eventArg = null; + _userSettingsService.SettingChanged += (sender, arg) => eventArg = arg; + + // Act + _userSettingsService.SetTheme(AppTheme.Dark); + + // Assert + _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Dark); + eventArg.Should().Be(nameof(UserSettings.Theme)); + _mockFileService.Verify(x => x.Save(_expectedSettingsPath, _appConfig.AppPropertiesFileName, It.IsAny()), Times.Once); + } + + [Fact] + public void SetShowLogs_ShouldUpdateSettingAndTriggerEvent() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings()); + _userSettingsService.Initialize(); + + string eventArg = null; + _userSettingsService.SettingChanged += (sender, arg) => eventArg = arg; + + // Act + _userSettingsService.SetShowLogs(true); + + // Assert + _userSettingsService.GetShowLogs().Should().BeTrue(); + eventArg.Should().Be(nameof(UserSettings.ShowLogs)); + } + + [Fact] + public void SetConfirmDeletion_ShouldUpdateSettingAndTriggerEvent() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings()); + _userSettingsService.Initialize(); + + string eventArg = null; + _userSettingsService.SettingChanged += (sender, arg) => eventArg = arg; + + // Act + _userSettingsService.SetConfirmDeletion(false); + + // Assert + _userSettingsService.GetConfirmDeletion().Should().BeFalse(); + eventArg.Should().Be(nameof(UserSettings.ConfirmDeletion)); + } + + [Fact] + public void GetSetting_ShouldReturnCorrectValue_ForTheme() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings { Theme = AppTheme.Light }); + _userSettingsService.Initialize(); + + // Act + var result = _userSettingsService.GetSetting(nameof(UserSettings.Theme)); + + // Assert + result.Should().Be(AppTheme.Light); + } + + [Fact] + public void GetSetting_ShouldReturnCorrectValue_ForShowLogs() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings { ShowLogs = true }); + _userSettingsService.Initialize(); + + // Act + var result = _userSettingsService.GetSetting(nameof(UserSettings.ShowLogs)); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public void GetSetting_ShouldReturnCorrectValue_ForConfirmDeletion() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings { ConfirmDeletion = false }); + _userSettingsService.Initialize(); + + // Act + var result = _userSettingsService.GetSetting(nameof(UserSettings.ConfirmDeletion)); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public void GetSetting_ShouldReturnDefaultValue_ForUnknownKey() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings()); + _userSettingsService.Initialize(); + + // Act + var result = _userSettingsService.GetSetting("UnknownKey", "DefaultValue"); + + // Assert + result.Should().Be("DefaultValue"); + } + + [Fact] + public void GetWindowSettings_ShouldReturnLoadedSettings() + { + // Arrange + var expectedSettings = new WindowSettings + { + Top = 200, + Left = 300, + Width = 1000, + Height = 800, + WindowState = WindowState.Maximized + }; + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, "WindowSettings.json")) + .Returns(expectedSettings); + + // Act + var result = _userSettingsService.GetWindowSettings(); + + // Assert + result.Should().BeEquivalentTo(expectedSettings); + } + + [Fact] + public void GetWindowSettings_ShouldReturnDefaultSettings_WhenFileServiceReturnsNull() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, "WindowSettings.json")) + .Returns((WindowSettings)null); + + // Act + var result = _userSettingsService.GetWindowSettings(); + + // Assert + result.Should().NotBeNull(); + result.Top.Should().Be(100); + result.Left.Should().Be(100); + result.Width.Should().Be(860); + result.Height.Should().Be(600); + result.WindowState.Should().Be(WindowState.Normal); + } + + [Fact] + public void SaveWindowsSettings_ShouldCallFileServiceSave() + { + // Arrange + var settings = new WindowSettings + { + Top = 150, + Left = 250, + Width = 900, + Height = 700, + WindowState = WindowState.Minimized + }; + + // Act + _userSettingsService.SaveWindowsSettings(settings); + + // Assert + _mockFileService.Verify(x => x.Save(_expectedSettingsPath, "WindowSettings.json", settings), Times.Once); + } + + [Fact] + public void GetTimeoutSettings_ShouldReturnLoadedSettings() + { + // Arrange + var expectedSettings = new TimeoutSettings + { + WaitAfterDelete = 1000, + WaitBetweenRetryDeleteAttempts = 750, + WaitAfterDocumentLoad = 5000 + }; + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, "timeoutSettings.json")) + .Returns(expectedSettings); + + // Act + var result = _userSettingsService.GetTimeoutSettings(); + + // Assert + result.Should().BeEquivalentTo(expectedSettings); + } + + [Fact] + public void GetTimeoutSettings_ShouldReturnDefaultSettings_WhenFileServiceReturnsNull() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, "timeoutSettings.json")) + .Returns((TimeoutSettings)null); + + // Act + var result = _userSettingsService.GetTimeoutSettings(); + + // Assert + result.Should().NotBeNull(); + result.WaitAfterDelete.Should().Be(500); + result.WaitBetweenRetryDeleteAttempts.Should().Be(500); + result.WaitAfterDocumentLoad.Should().Be(3000); + } + + [Fact] + public void SaveTimeoutSettings_ShouldCallFileServiceSave() + { + // Arrange + var settings = new TimeoutSettings + { + WaitAfterDelete = 800, + WaitBetweenRetryDeleteAttempts = 600, + WaitAfterDocumentLoad = 4000 + }; + + // Act + _userSettingsService.SaveTimeoutSettings(settings); + + // Assert + _mockFileService.Verify(x => x.Save(_expectedSettingsPath, "timeoutSettings.json", settings), Times.Once); + } + + [Fact] + public void GetCurrentTheme_ShouldReturnCurrentTheme_AfterInitialization() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings { Theme = AppTheme.Light }); + + // Act + _userSettingsService.Initialize(); + var result = _userSettingsService.GetCurrentTheme(); + + // Assert + result.Should().Be(AppTheme.Light); + } + + [Fact] + public void GetShowLogs_ShouldReturnCurrentValue_AfterInitialization() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings { ShowLogs = true }); + + // Act + _userSettingsService.Initialize(); + var result = _userSettingsService.GetShowLogs(); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public void GetConfirmDeletion_ShouldReturnCurrentValue_AfterInitialization() + { + // Arrange + _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) + .Returns(new UserSettings { ConfirmDeletion = false }); + + // Act + _userSettingsService.Initialize(); + var result = _userSettingsService.GetConfirmDeletion(); + + // Assert + result.Should().BeFalse(); + } +} \ No newline at end of file From a2e788c9f23ba5c703b8292bbde38a03687b7baa Mon Sep 17 00:00:00 2001 From: thorstenalpers Date: Fri, 30 May 2025 15:37:26 +0200 Subject: [PATCH 2/5] Fix tests --- src/Tests/Services/NavigationServiceTests.cs | 271 ------------------ src/Tests/Services/PageServiceTests.cs | 2 +- .../Services/UserSettingsServiceTests.cs | 128 +-------- src/Tests/Services/XScriptServiceTests.cs | 1 + src/Tests/ViewModels/XViewModelTests.cs | 248 ++++++++-------- src/UI/Services/UserSettingsService.cs | 11 +- 6 files changed, 143 insertions(+), 518 deletions(-) delete mode 100644 src/Tests/Services/NavigationServiceTests.cs diff --git a/src/Tests/Services/NavigationServiceTests.cs b/src/Tests/Services/NavigationServiceTests.cs deleted file mode 100644 index a08445e..0000000 --- a/src/Tests/Services/NavigationServiceTests.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System.Windows.Controls; -using System.Windows.Navigation; -using CleanMyPosts.UI.Contracts.Services; -using CleanMyPosts.UI.Contracts.ViewModels; -using FluentAssertions; -using Moq; -using Xunit; - -namespace CleanMyPosts.Tests.Services; - -[Trait("Category", "Unit")] -public class NavigationServiceTests -{ - private readonly Mock _mockPageService; - private readonly Mock _mockFrame; - private readonly CleanMyPosts.UI.Services.NavigationService _navigationService; - - public NavigationServiceTests() - { - _mockPageService = new Mock(); - _mockFrame = new Mock(); - _navigationService = new CleanMyPosts.UI.Services.NavigationService(_mockPageService.Object); - } - - [Fact] - public void Constructor_ShouldInitializeWithPageService() - { - // Act & Assert - _navigationService.Should().NotBeNull(); - } - - [Fact] - public void CanGoBack_ShouldReturnFalse_WhenFrameIsNotInitialized() - { - // Act - var result = _navigationService.CanGoBack; - - // Assert - result.Should().BeFalse(); - } - - [Fact] - public void CanGoBack_ShouldReturnFrameCanGoBack_WhenFrameIsInitialized() - { - // Arrange - _mockFrame.Setup(x => x.CanGoBack).Returns(true); - _navigationService.Initialize(_mockFrame.Object); - - // Act - var result = _navigationService.CanGoBack; - - // Assert - result.Should().BeTrue(); - } - - [Fact] - public void Initialize_ShouldSetFrame_WhenCalledFirstTime() - { - // Act - _navigationService.Initialize(_mockFrame.Object); - - // Assert - _mockFrame.VerifyAdd(x => x.Navigated += It.IsAny(), Times.Once); - } - - [Fact] - public void Initialize_ShouldNotSetFrame_WhenCalledMultipleTimes() - { - // Arrange - var secondFrame = new Mock(); - - // Act - _navigationService.Initialize(_mockFrame.Object); - _navigationService.Initialize(secondFrame.Object); - - // Assert - _mockFrame.VerifyAdd(x => x.Navigated += It.IsAny(), Times.Once); - secondFrame.VerifyAdd(x => x.Navigated += It.IsAny(), Times.Never); - } - - [Fact] - public void UnsubscribeNavigation_ShouldUnsubscribeAndClearFrame() - { - // Arrange - _navigationService.Initialize(_mockFrame.Object); - - // Act - _navigationService.UnsubscribeNavigation(); - - // Assert - _mockFrame.VerifyRemove(x => x.Navigated -= It.IsAny(), Times.Once); - } - - [Fact] - public void GoBack_ShouldCallFrameGoBack_WhenCanGoBackIsTrue() - { - // Arrange - _mockFrame.Setup(x => x.CanGoBack).Returns(true); - _navigationService.Initialize(_mockFrame.Object); - - // Act - _navigationService.GoBack(); - - // Assert - _mockFrame.Verify(x => x.GoBack(), Times.Once); - } - - [Fact] - public void GoBack_ShouldNotCallFrameGoBack_WhenCanGoBackIsFalse() - { - // Arrange - _mockFrame.Setup(x => x.CanGoBack).Returns(false); - _navigationService.Initialize(_mockFrame.Object); - - // Act - _navigationService.GoBack(); - - // Assert - _mockFrame.Verify(x => x.GoBack(), Times.Never); - } - - [Fact] - public void NavigateTo_ShouldReturnTrue_WhenNavigationSucceeds() - { - // Arrange - var pageKey = "TestPage"; - var pageType = typeof(Page); - var mockPage = new Mock(); - - _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); - _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); - _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(true); - _mockFrame.Setup(x => x.Content).Returns(null); - - _navigationService.Initialize(_mockFrame.Object); - - // Act - var result = _navigationService.NavigateTo(pageKey); - - // Assert - result.Should().BeTrue(); - _mockFrame.Verify(x => x.Navigate(mockPage.Object, null), Times.Once); - } - - [Fact] - public void NavigateTo_ShouldReturnFalse_WhenNavigationFails() - { - // Arrange - var pageKey = "TestPage"; - var pageType = typeof(Page); - var mockPage = new Mock(); - - _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); - _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); - _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(false); - _mockFrame.Setup(x => x.Content).Returns(null); - - _navigationService.Initialize(_mockFrame.Object); - - // Act - var result = _navigationService.NavigateTo(pageKey); - - // Assert - result.Should().BeFalse(); - } - - [Fact] - public void NavigateTo_ShouldReturnFalse_WhenSamePageAndNoParameter() - { - // Arrange - var pageKey = "TestPage"; - var pageType = typeof(Page); - var existingPage = new Mock(); - existingPage.Setup(x => x.GetType()).Returns(pageType); - - _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); - _mockFrame.Setup(x => x.Content).Returns(existingPage.Object); - - _navigationService.Initialize(_mockFrame.Object); - - // Act - var result = _navigationService.NavigateTo(pageKey); - - // Assert - result.Should().BeFalse(); - _mockFrame.Verify(x => x.Navigate(It.IsAny(), It.IsAny()), Times.Never); - } - - [Fact] - public void NavigateTo_ShouldSetClearNavigationTag_WhenClearNavigationIsTrue() - { - // Arrange - var pageKey = "TestPage"; - var pageType = typeof(Page); - var mockPage = new Mock(); - - _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); - _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); - _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(true); - _mockFrame.Setup(x => x.Content).Returns(null); - - _navigationService.Initialize(_mockFrame.Object); - - // Act - _navigationService.NavigateTo(pageKey, null, clearNavigation: true); - - // Assert - _mockFrame.VerifySet(x => x.Tag = true, Times.Once); - } - - [Fact] - public void NavigateTo_ShouldNavigateWithParameter() - { - // Arrange - var pageKey = "TestPage"; - var pageType = typeof(Page); - var mockPage = new Mock(); - var parameter = "test parameter"; - - _mockPageService.Setup(x => x.GetPageType(pageKey)).Returns(pageType); - _mockPageService.Setup(x => x.GetPage(pageKey)).Returns(mockPage.Object); - _mockFrame.Setup(x => x.Navigate(It.IsAny(), It.IsAny())).Returns(true); - _mockFrame.Setup(x => x.Content).Returns(null); - - _navigationService.Initialize(_mockFrame.Object); - - // Act - var result = _navigationService.NavigateTo(pageKey, parameter); - - // Assert - result.Should().BeTrue(); - _mockFrame.Verify(x => x.Navigate(mockPage.Object, parameter), Times.Once); - } - - [Fact] - public void CleanNavigation_ShouldCallFrameCleanNavigation() - { - // Arrange - _navigationService.Initialize(_mockFrame.Object); - - // Act - _navigationService.CleanNavigation(); - - // Assert - This would require the extension method to be mockable - // For now, we just verify the method doesn't throw - var action = () => _navigationService.CleanNavigation(); - action.Should().NotThrow(); - } - - [Fact] - public void NavigatedEvent_ShouldBeTriggered_WhenFrameNavigated() - { - // Arrange - _navigationService.Initialize(_mockFrame.Object); - - string eventArg = null; - object eventSender = null; - _navigationService.Navigated += (sender, arg) => - { - eventSender = sender; - eventArg = arg; - }; - - // Act - This would require more complex setup to properly test the event - // For now, we verify the event can be subscribed to - var action = () => _navigationService.Navigated += (s, e) => { }; - - // Assert - action.Should().NotThrow(); - } -} \ No newline at end of file diff --git a/src/Tests/Services/PageServiceTests.cs b/src/Tests/Services/PageServiceTests.cs index ed3729d..e8413d4 100644 --- a/src/Tests/Services/PageServiceTests.cs +++ b/src/Tests/Services/PageServiceTests.cs @@ -71,7 +71,7 @@ public void GetPageType_ShouldThrowArgumentException_ForUnknownKey() .WithMessage($"Page not found: {unknownKey}. Did you forget to call PageService.Configure?"); } - [Fact] + [StaFact] public void GetPage_ShouldReturnPageFromServiceProvider() { // Arrange diff --git a/src/Tests/Services/UserSettingsServiceTests.cs b/src/Tests/Services/UserSettingsServiceTests.cs index 4adf694..8f0eea7 100644 --- a/src/Tests/Services/UserSettingsServiceTests.cs +++ b/src/Tests/Services/UserSettingsServiceTests.cs @@ -1,9 +1,9 @@ +using System.Windows; using CleanMyPosts.Core.Contracts.Services; using CleanMyPosts.UI.Models; using CleanMyPosts.UI.Services; using FluentAssertions; using Moq; -using System.Windows; using Xunit; namespace CleanMyPosts.Tests.Services; @@ -19,34 +19,15 @@ public class UserSettingsServiceTests public UserSettingsServiceTests() { _mockFileService = new Mock(); - _appConfig = new AppConfig(); - _expectedSettingsPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - _appConfig.ConfigurationsFolder); - _userSettingsService = new UserSettingsService(_mockFileService.Object, _appConfig); - } - - [Fact] - public void Initialize_ShouldLoadSettings_WhenCalled() - { - // Arrange - var expectedSettings = new UserSettings + _appConfig = new AppConfig { - Theme = AppTheme.Dark, - ShowLogs = true, - ConfirmDeletion = false + DarkStyleUri = null, + LightStyleUri = null, }; - _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) - .Returns(expectedSettings); - - // Act - _userSettingsService.Initialize(); - - // Assert - _mockFileService.Verify(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName), Times.Once); - _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Dark); - _userSettingsService.GetShowLogs().Should().BeTrue(); - _userSettingsService.GetConfirmDeletion().Should().BeFalse(); + _expectedSettingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + _appConfig.ConfigurationsFolder); + _userSettingsService = new UserSettingsService(_mockFileService.Object, _appConfig); } [Fact] @@ -80,47 +61,6 @@ public void PersistData_ShouldSaveCurrentSettings() _mockFileService.Verify(x => x.Save(_expectedSettingsPath, _appConfig.AppPropertiesFileName, It.IsAny()), Times.Once); } - [Fact] - public void RestoreData_ShouldReloadSettings() - { - // Arrange - var initialSettings = new UserSettings { Theme = AppTheme.Light }; - var restoredSettings = new UserSettings { Theme = AppTheme.Dark }; - - _mockFileService.SetupSequence(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) - .Returns(initialSettings) - .Returns(restoredSettings); - - _userSettingsService.Initialize(); - _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Light); - - // Act - _userSettingsService.RestoreData(); - - // Assert - _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Dark); - } - - [Fact] - public void SetTheme_ShouldUpdateThemeAndTriggerEvent() - { - // Arrange - _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) - .Returns(new UserSettings()); - _userSettingsService.Initialize(); - - string eventArg = null; - _userSettingsService.SettingChanged += (sender, arg) => eventArg = arg; - - // Act - _userSettingsService.SetTheme(AppTheme.Dark); - - // Assert - _userSettingsService.GetCurrentTheme().Should().Be(AppTheme.Dark); - eventArg.Should().Be(nameof(UserSettings.Theme)); - _mockFileService.Verify(x => x.Save(_expectedSettingsPath, _appConfig.AppPropertiesFileName, It.IsAny()), Times.Once); - } - [Fact] public void SetShowLogs_ShouldUpdateSettingAndTriggerEvent() { @@ -159,21 +99,6 @@ public void SetConfirmDeletion_ShouldUpdateSettingAndTriggerEvent() eventArg.Should().Be(nameof(UserSettings.ConfirmDeletion)); } - [Fact] - public void GetSetting_ShouldReturnCorrectValue_ForTheme() - { - // Arrange - _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) - .Returns(new UserSettings { Theme = AppTheme.Light }); - _userSettingsService.Initialize(); - - // Act - var result = _userSettingsService.GetSetting(nameof(UserSettings.Theme)); - - // Assert - result.Should().Be(AppTheme.Light); - } - [Fact] public void GetSetting_ShouldReturnCorrectValue_ForShowLogs() { @@ -219,28 +144,6 @@ public void GetSetting_ShouldReturnDefaultValue_ForUnknownKey() result.Should().Be("DefaultValue"); } - [Fact] - public void GetWindowSettings_ShouldReturnLoadedSettings() - { - // Arrange - var expectedSettings = new WindowSettings - { - Top = 200, - Left = 300, - Width = 1000, - Height = 800, - WindowState = WindowState.Maximized - }; - _mockFileService.Setup(x => x.Read(_expectedSettingsPath, "WindowSettings.json")) - .Returns(expectedSettings); - - // Act - var result = _userSettingsService.GetWindowSettings(); - - // Assert - result.Should().BeEquivalentTo(expectedSettings); - } - [Fact] public void GetWindowSettings_ShouldReturnDefaultSettings_WhenFileServiceReturnsNull() { @@ -335,21 +238,6 @@ public void SaveTimeoutSettings_ShouldCallFileServiceSave() _mockFileService.Verify(x => x.Save(_expectedSettingsPath, "timeoutSettings.json", settings), Times.Once); } - [Fact] - public void GetCurrentTheme_ShouldReturnCurrentTheme_AfterInitialization() - { - // Arrange - _mockFileService.Setup(x => x.Read(_expectedSettingsPath, _appConfig.AppPropertiesFileName)) - .Returns(new UserSettings { Theme = AppTheme.Light }); - - // Act - _userSettingsService.Initialize(); - var result = _userSettingsService.GetCurrentTheme(); - - // Assert - result.Should().Be(AppTheme.Light); - } - [Fact] public void GetShowLogs_ShouldReturnCurrentValue_AfterInitialization() { diff --git a/src/Tests/Services/XScriptServiceTests.cs b/src/Tests/Services/XScriptServiceTests.cs index 15b2e88..6881379 100644 --- a/src/Tests/Services/XScriptServiceTests.cs +++ b/src/Tests/Services/XScriptServiceTests.cs @@ -8,6 +8,7 @@ namespace CleanMyPosts.UI.Tests.Services; +[Trait("Category", "Unit")] public class XScriptServiceTests { private readonly Mock> _loggerMock = new(); diff --git a/src/Tests/ViewModels/XViewModelTests.cs b/src/Tests/ViewModels/XViewModelTests.cs index cf4c647..738d214 100644 --- a/src/Tests/ViewModels/XViewModelTests.cs +++ b/src/Tests/ViewModels/XViewModelTests.cs @@ -8,131 +8,131 @@ using Moq; using Xunit; -namespace CleanMyPosts.UI.Tests.ViewModels +namespace CleanMyPosts.UI.Tests.ViewModels; + +[Trait("Category", "Unit")] +public class XViewModelTests { - public class XViewModelTests + private readonly Mock _webViewHostServiceMock; + private readonly Mock _userSettingsServiceMock; + private readonly Mock> _loggerMock; + private readonly Mock _xWebViewScriptServiceMock; + private readonly Mock _dialogCoordinatorMock; + private readonly Mock _appConfigMock; + + public XViewModelTests() + { + _webViewHostServiceMock = new Mock(); + _loggerMock = new Mock>(); + _userSettingsServiceMock = new Mock(); + _xWebViewScriptServiceMock = new Mock(); + _dialogCoordinatorMock = new Mock(); + _appConfigMock = new Mock(); + } + + private XViewModel CreateViewModel() + { + return new XViewModel( + _loggerMock.Object, + _userSettingsServiceMock.Object, + _webViewHostServiceMock.Object, + _dialogCoordinatorMock.Object, + _appConfigMock.Object, + _xWebViewScriptServiceMock.Object + ); + } + + private static void InvokePrivateMethod(object obj, string methodName, params object[] parameters) + { + var method = obj.GetType().GetMethod(methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + method.Invoke(obj, parameters); + } + + [StaFact] + public async Task OnNavigationCompleted_UserLoggedIn_EnablesButtons() + { + var viewModel = CreateViewModel(); + _xWebViewScriptServiceMock.SetupSequence(x => x.GetUserNameAsync()) + .ReturnsAsync((string)null) + .ReturnsAsync("user"); + + var args = new NavigationCompletedEventArgs { IsSuccess = true }; + + await viewModel.InitializeAsync(new WebView2()); + InvokePrivateMethod(viewModel, "OnNavigationCompleted", this, args); + + await Task.Delay(600); // Wait for async handler + + Assert.True(viewModel.AreButtonsEnabled); + } + + [Fact] + public void OnWebMessageReceived_LogsErrorWarningInfo() { - private readonly Mock _webViewHostServiceMock; - private readonly Mock _userSettingsServiceMock; - private readonly Mock> _loggerMock; - private readonly Mock _xWebViewScriptServiceMock; - private readonly Mock _dialogCoordinatorMock; - private readonly Mock _appConfigMock; - - public XViewModelTests() - { - _webViewHostServiceMock = new Mock(); - _loggerMock = new Mock>(); - _userSettingsServiceMock = new Mock(); - _xWebViewScriptServiceMock = new Mock(); - _dialogCoordinatorMock = new Mock(); - _appConfigMock = new Mock(); - } - - private XViewModel CreateViewModel() - { - return new XViewModel( - _loggerMock.Object, - _userSettingsServiceMock.Object, - _webViewHostServiceMock.Object, - _dialogCoordinatorMock.Object, - _appConfigMock.Object, - _xWebViewScriptServiceMock.Object - ); - } - - private static void InvokePrivateMethod(object obj, string methodName, params object[] parameters) - { - var method = obj.GetType().GetMethod(methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - method.Invoke(obj, parameters); - } - - [StaFact] - public async Task OnNavigationCompleted_UserLoggedIn_EnablesButtons() - { - var viewModel = CreateViewModel(); - _xWebViewScriptServiceMock.SetupSequence(x => x.GetUserNameAsync()) - .ReturnsAsync((string)null) - .ReturnsAsync("user"); - - var args = new NavigationCompletedEventArgs { IsSuccess = true }; - - await viewModel.InitializeAsync(new WebView2()); - InvokePrivateMethod(viewModel, "OnNavigationCompleted", this, args); - - await Task.Delay(600); // Wait for async handler - - Assert.True(viewModel.AreButtonsEnabled); - } - - [Fact] - public void OnWebMessageReceived_LogsErrorWarningInfo() - { - var viewModel = CreateViewModel(); - var errorMsg = "{\"level\":\"error\",\"message\":\"err\"}"; - var warnMsg = "{\"level\":\"warning\",\"message\":\"warn\"}"; - var infoMsg = "{\"level\":\"info\",\"message\":\"info\"}"; - - var errorArgs = new WebMessageReceivedEventArgs { Message = errorMsg }; - var warnArgs = new WebMessageReceivedEventArgs { Message = warnMsg }; - var infoArgs = new WebMessageReceivedEventArgs { Message = infoMsg }; - - InvokePrivateMethod(viewModel, "OnWebMessageReceived", this, errorArgs); - InvokePrivateMethod(viewModel, "OnWebMessageReceived", this, warnArgs); - InvokePrivateMethod(viewModel, "OnWebMessageReceived", this, infoArgs); - - _loggerMock.Verify(l => l.Log( - LogLevel.Error, It.IsAny(), It.Is((v, t) => v.ToString().Contains("err")), null, It.IsAny>()), Times.Once); - _loggerMock.Verify(l => l.Log( - LogLevel.Warning, It.IsAny(), It.Is((v, t) => v.ToString().Contains("warn")), null, It.IsAny>()), Times.Once); - _loggerMock.Verify(l => l.Log( - LogLevel.Information, It.IsAny(), It.Is((v, t) => v.ToString().Contains("info")), null, It.IsAny>()), Times.Once); - } - - - [StaFact] - public async Task ShowPostsCommand_CallsShowPostsAsync_EnablesUserInteractions() - { - // Arrange - var viewModel = CreateViewModel(); - var command = viewModel.GetType().GetProperty("ShowPostsCommand").GetValue(viewModel) as IAsyncRelayCommand; - - // Act - await command.ExecuteAsync(null); - - // Assert - _xWebViewScriptServiceMock.Verify(x => x.ShowPostsAsync(), Times.Once); - Assert.True(viewModel.AreButtonsEnabled); - } - - [StaFact] - public async Task ShowLikesCommand_CallsShowLikesAsync_EnablesUserInteractions() - { - // Arrange - var viewModel = CreateViewModel(); - var command = viewModel.GetType().GetProperty("ShowLikesCommand").GetValue(viewModel) as IAsyncRelayCommand; - - // Act - await command.ExecuteAsync(null); - - // Assert - _xWebViewScriptServiceMock.Verify(x => x.ShowLikesAsync(), Times.Once); - Assert.True(viewModel.AreButtonsEnabled); - } - - [StaFact] - public async Task ShowFollowingCommand_CallsShowFollowingAsync_EnablesUserInteractions() - { - // Arrange - var viewModel = CreateViewModel(); - var command = viewModel.GetType().GetProperty("ShowFollowingCommand").GetValue(viewModel) as IAsyncRelayCommand; - - // Act - await command.ExecuteAsync(null); - - // Assert - _xWebViewScriptServiceMock.Verify(x => x.ShowFollowingAsync(), Times.Once); - Assert.True(viewModel.AreButtonsEnabled); - } + var viewModel = CreateViewModel(); + var errorMsg = "{\"level\":\"error\",\"message\":\"err\"}"; + var warnMsg = "{\"level\":\"warning\",\"message\":\"warn\"}"; + var infoMsg = "{\"level\":\"info\",\"message\":\"info\"}"; + + var errorArgs = new WebMessageReceivedEventArgs { Message = errorMsg }; + var warnArgs = new WebMessageReceivedEventArgs { Message = warnMsg }; + var infoArgs = new WebMessageReceivedEventArgs { Message = infoMsg }; + + InvokePrivateMethod(viewModel, "OnWebMessageReceived", this, errorArgs); + InvokePrivateMethod(viewModel, "OnWebMessageReceived", this, warnArgs); + InvokePrivateMethod(viewModel, "OnWebMessageReceived", this, infoArgs); + + _loggerMock.Verify(l => l.Log( + LogLevel.Error, It.IsAny(), It.Is((v, t) => v.ToString().Contains("err")), null, It.IsAny>()), Times.Once); + _loggerMock.Verify(l => l.Log( + LogLevel.Warning, It.IsAny(), It.Is((v, t) => v.ToString().Contains("warn")), null, It.IsAny>()), Times.Once); + _loggerMock.Verify(l => l.Log( + LogLevel.Information, It.IsAny(), It.Is((v, t) => v.ToString().Contains("info")), null, It.IsAny>()), Times.Once); + } + + + [StaFact] + public async Task ShowPostsCommand_CallsShowPostsAsync_EnablesUserInteractions() + { + // Arrange + var viewModel = CreateViewModel(); + var command = viewModel.GetType().GetProperty("ShowPostsCommand").GetValue(viewModel) as IAsyncRelayCommand; + + // Act + await command.ExecuteAsync(null); + + // Assert + _xWebViewScriptServiceMock.Verify(x => x.ShowPostsAsync(), Times.Once); + Assert.True(viewModel.AreButtonsEnabled); + } + + [StaFact] + public async Task ShowLikesCommand_CallsShowLikesAsync_EnablesUserInteractions() + { + // Arrange + var viewModel = CreateViewModel(); + var command = viewModel.GetType().GetProperty("ShowLikesCommand").GetValue(viewModel) as IAsyncRelayCommand; + + // Act + await command.ExecuteAsync(null); + + // Assert + _xWebViewScriptServiceMock.Verify(x => x.ShowLikesAsync(), Times.Once); + Assert.True(viewModel.AreButtonsEnabled); + } + + [StaFact] + public async Task ShowFollowingCommand_CallsShowFollowingAsync_EnablesUserInteractions() + { + // Arrange + var viewModel = CreateViewModel(); + var command = viewModel.GetType().GetProperty("ShowFollowingCommand").GetValue(viewModel) as IAsyncRelayCommand; + + // Act + await command.ExecuteAsync(null); + + // Assert + _xWebViewScriptServiceMock.Verify(x => x.ShowFollowingAsync(), Times.Once); + Assert.True(viewModel.AreButtonsEnabled); } } diff --git a/src/UI/Services/UserSettingsService.cs b/src/UI/Services/UserSettingsService.cs index af62146..72afc89 100644 --- a/src/UI/Services/UserSettingsService.cs +++ b/src/UI/Services/UserSettingsService.cs @@ -71,12 +71,19 @@ public void SetConfirmDeletion(bool value) private void AddCustomThemes() { - ThemeManager.Current.AddLibraryTheme(new LibraryTheme( + if (_appConfig.DarkStyleUri != null) + { + ThemeManager.Current.AddLibraryTheme(new LibraryTheme( new Uri(_appConfig.DarkStyleUri), MahAppsLibraryThemeProvider.DefaultInstance)); - ThemeManager.Current.AddLibraryTheme(new LibraryTheme( + } + + if (_appConfig.LightStyleUri != null) + { + ThemeManager.Current.AddLibraryTheme(new LibraryTheme( new Uri(_appConfig.LightStyleUri), MahAppsLibraryThemeProvider.DefaultInstance)); + } } private static void ConfigureThemeSyncMode(AppTheme theme) From f3c7953f6c98675fdb5106043638fb33ea1153dc Mon Sep 17 00:00:00 2001 From: thorstenalpers Date: Wed, 25 Jun 2025 17:49:39 +0200 Subject: [PATCH 3/5] Add donate button --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 740eb64..cb16c5a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Windows](https://img.shields.io/badge/platform-Windows-blue)](#) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE.txt) [![CI Tests](https://github.com/thorstenalpers/CleanMyPosts/actions/workflows/ci.yml/badge.svg)](https://github.com/thorstenalpers/CleanMyPosts/actions/workflows/ci.yml) +[![Donate](https://img.shields.io/badge/donate-PayPal-yellow)](https://www.paypal.com/donate/?hosted_button_id=QYHGE9LA9SNAN) [![Star this repo](https://img.shields.io/github/stars/thorstenalpers/CleanMyPosts.svg?style=social&label=Star&maxAge=60)](https://github.com/thorstenalpers/CleanMyPosts) **CleanMyPosts** is a lightweight Windows desktop app that securely deletes all posts, reposts, replies, likes, and followings from your X (formerly Twitter) account in bulk using browser automation. From 787f5d8c71252d39facbc206dd767a7f3c0ed533 Mon Sep 17 00:00:00 2001 From: thorstenalpers Date: Sun, 6 Jul 2025 17:31:37 +0200 Subject: [PATCH 4/5] Fix: Add correct parameter into DeleteAllReplies() with USERNAME --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb16c5a..7e374d2 100644 --- a/README.md +++ b/README.md @@ -103,13 +103,13 @@ You can also run the cleanup directly in your browser using JavaScript snippets: #### Delete Reposts - URL: [https://x.com/USERNAME](https://x.com/USERNAME) - Script: [delete-all-reposts.js](https://raw.githubusercontent.com/thorstenalpers/CleanMyPosts/refs/heads/main/src/UI/Scripts/delete-all-reposts.js) -- Run: `DeleteAllRepost(1000, 1000, 5);` +- Run: `DeleteAllRepost(1000);` #### Delete Replies - URL: [https://x.com/USERNAME/with_replies](https://x.com/USERNAME/with_replies) - Script: [delete-all-replies.js](https://raw.githubusercontent.com/thorstenalpers/CleanMyPosts/refs/heads/main/src/UI/Scripts/delete-all-replies.js) -- Run: `DeleteAllReplies(1000, 1000, 5);` +- Run: `DeleteAllReplies('USERNAME', 1000, 5);` // replace USERNAME with yours #### Unlike Posts - URL: [https://x.com/USERNAME/likes](https://x.com/USERNAME/likes) From b05a6a7a8404fa3413296079dfd5e2006d4f41e4 Mon Sep 17 00:00:00 2001 From: thorstenalpers Date: Sun, 6 Jul 2025 17:37:11 +0200 Subject: [PATCH 5/5] Decrease retry attempts from 5 to 3 --- src/UI/Services/XScriptService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UI/Services/XScriptService.cs b/src/UI/Services/XScriptService.cs index e14baa8..3f76e6b 100644 --- a/src/UI/Services/XScriptService.cs +++ b/src/UI/Services/XScriptService.cs @@ -299,7 +299,7 @@ private async Task RunDeleteScriptAsync(string url, bool isArticle, string int retryCount = 0, deletedItems = 0; - while ((isArticle ? await IsAnArticlePresentAsync() : !await IsEmptyMessagePresentAsync()) && retryCount++ < 5) + while ((isArticle ? await IsAnArticlePresentAsync() : !await IsEmptyMessagePresentAsync()) && retryCount++ < 3) { try {