diff --git a/README.md b/README.md index 740eb64..7e374d2 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. @@ -102,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) 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/PageServiceTests.cs b/src/Tests/Services/PageServiceTests.cs new file mode 100644 index 0000000..e8413d4 --- /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?"); + } + + [StaFact] + 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..8f0eea7 --- /dev/null +++ b/src/Tests/Services/UserSettingsServiceTests.cs @@ -0,0 +1,270 @@ +using System.Windows; +using CleanMyPosts.Core.Contracts.Services; +using CleanMyPosts.UI.Models; +using CleanMyPosts.UI.Services; +using FluentAssertions; +using Moq; +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 + { + DarkStyleUri = null, + LightStyleUri = null, + }; + _expectedSettingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + _appConfig.ConfigurationsFolder); + _userSettingsService = new UserSettingsService(_mockFileService.Object, _appConfig); + } + + [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 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_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_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 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 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) 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 {