Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions release-notes/v2.0.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
### What's Changed

- **Fixed Bug in Repost Deletion Retry Loop**
Improved handling of reposts where the deletion loop could appear to hang. The retry count has been reduced from 5 to 3 to prevent perceived infinite loops when a repost cannot be removed.
👉 *Tip: Delete your original posts first before cleaning up reposts to avoid mixed-content issues.*

- **Fixed Documentation Bug in `DeleteAllReplies` Example**
The example for `DeleteAllReplies` was missing the required `username` parameter, causing the script to fail. The corrected usage is:
```js
DeleteAllReplies('your_username', 1000, 5);
50 changes: 50 additions & 0 deletions src/Tests/Services/FileServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
155 changes: 155 additions & 0 deletions src/Tests/Services/PageServiceTests.cs
Original file line number Diff line number Diff line change
@@ -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<IServiceProvider> _mockServiceProvider;
private readonly PageService _pageService;

public PageServiceTests()
{
_mockServiceProvider = new Mock<IServiceProvider>();
_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<ArgumentException>()
.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<Page>().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<ArgumentException>()
.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));
}
}
Loading