From f49d276f5aadcb2f7526fa341de73f1d8e46dc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Tue, 17 Feb 2026 15:45:29 +0100 Subject: [PATCH] Add dedicated Wino.Mail.ViewModels.Tests coverage for WinoMailCollection (#812) * Add WinoMailCollection tests in dedicated ViewModels test project * Fix WinoMailCollection tests flattening without SelectMany --- .../Collections/WinoMailCollectionTests.cs | 157 ++++++++++++++++++ Wino.Mail.ViewModels.Tests/GlobalUsings.cs | 4 + .../Wino.Mail.ViewModels.Tests.csproj | 24 +++ WinoMail.slnx | 5 + 4 files changed, 190 insertions(+) create mode 100644 Wino.Mail.ViewModels.Tests/Collections/WinoMailCollectionTests.cs create mode 100644 Wino.Mail.ViewModels.Tests/GlobalUsings.cs create mode 100644 Wino.Mail.ViewModels.Tests/Wino.Mail.ViewModels.Tests.csproj diff --git a/Wino.Mail.ViewModels.Tests/Collections/WinoMailCollectionTests.cs b/Wino.Mail.ViewModels.Tests/Collections/WinoMailCollectionTests.cs new file mode 100644 index 00000000..23ae4817 --- /dev/null +++ b/Wino.Mail.ViewModels.Tests/Collections/WinoMailCollectionTests.cs @@ -0,0 +1,157 @@ +using FluentAssertions; +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Interfaces; +using Wino.Mail.ViewModels.Collections; +using Wino.Mail.ViewModels.Data; +using Xunit; + +namespace Wino.Mail.ViewModels.Tests.Collections; + +public class WinoMailCollectionTests +{ + [Fact] + public async Task AddAsync_ShouldAddSingleItemAsMailItemViewModel() + { + var sut = CreateCollection(); + var mail = CreateMailCopy(threadId: "thread-1"); + + await sut.AddAsync(mail); + + var items = FlattenItems(sut); + items.Should().ContainSingle().Which.Should().BeOfType(); + sut.ContainsMailUniqueId(mail.UniqueId).Should().BeTrue(); + } + + [Fact] + public async Task AddAsync_ShouldKeepItemsSeparate_WhenThreadIdsDiffer() + { + var sut = CreateCollection(); + var first = CreateMailCopy(threadId: "thread-1"); + var second = CreateMailCopy(threadId: "thread-2"); + + await sut.AddAsync(first); + await sut.AddAsync(second); + + var items = FlattenItems(sut); + items.Should().HaveCount(2); + items.Should().OnlyContain(item => item is MailItemViewModel); + } + + [Fact] + public async Task AddAsync_ShouldConvertSingleItemToThread_WhenSecondItemWithSameThreadIdIsAdded() + { + var sut = CreateCollection(); + var first = CreateMailCopy(threadId: "shared-thread", creationDate: DateTime.UtcNow.AddMinutes(-1)); + var second = CreateMailCopy(threadId: "shared-thread", creationDate: DateTime.UtcNow); + + await sut.AddAsync(first); + FlattenItems(sut).Should().ContainSingle().Which.Should().BeOfType(); + + await sut.AddAsync(second); + + var items = FlattenItems(sut); + var threadItem = items.Should().ContainSingle().Which.Should().BeOfType().Subject; + threadItem.EmailCount.Should().Be(2); + threadItem.GetContainingIds().Should().BeEquivalentTo([first.UniqueId, second.UniqueId]); + } + + [Fact] + public async Task RemoveAsync_ShouldConvertThreadToSingleItem_WhenThreadDropsToOneItem() + { + var sut = CreateCollection(); + var first = CreateMailCopy(threadId: "shared-thread", creationDate: DateTime.UtcNow.AddMinutes(-1)); + var second = CreateMailCopy(threadId: "shared-thread", creationDate: DateTime.UtcNow); + + await sut.AddAsync(first); + await sut.AddAsync(second); + + await sut.RemoveAsync(second); + + var items = FlattenItems(sut); + var remainingItem = items.Should().ContainSingle().Which.Should().BeOfType().Subject; + remainingItem.MailCopy.UniqueId.Should().Be(first.UniqueId); + + var container = sut.GetMailItemContainer(first.UniqueId); + container.Should().NotBeNull(); + container.ThreadViewModel.Should().BeNull(); + } + + [Fact] + public async Task RemoveAsync_ShouldRemoveSingleItem() + { + var sut = CreateCollection(); + var mail = CreateMailCopy(threadId: "thread-1"); + + await sut.AddAsync(mail); + await sut.RemoveAsync(mail); + + FlattenItems(sut).Should().BeEmpty(); + sut.ContainsMailUniqueId(mail.UniqueId).Should().BeFalse(); + } + + [Fact] + public async Task AddRangeAsync_ShouldCreateThreadsForItemsWithMatchingThreadId() + { + var sut = CreateCollection(); + + var threadAFirst = new MailItemViewModel(CreateMailCopy(threadId: "thread-a", creationDate: DateTime.UtcNow.AddMinutes(-10))); + var threadASecond = new MailItemViewModel(CreateMailCopy(threadId: "thread-a", creationDate: DateTime.UtcNow.AddMinutes(-9))); + var threadCFirst = new MailItemViewModel(CreateMailCopy(threadId: "thread-c", creationDate: DateTime.UtcNow.AddMinutes(-8))); + var threadCSecond = new MailItemViewModel(CreateMailCopy(threadId: "thread-c", creationDate: DateTime.UtcNow.AddMinutes(-7))); + var single = new MailItemViewModel(CreateMailCopy(threadId: "thread-b", creationDate: DateTime.UtcNow.AddMinutes(-6))); + + await sut.AddRangeAsync([threadAFirst, threadASecond, threadCFirst, threadCSecond, single], clearIdCache: true); + + var items = FlattenItems(sut); + items.Should().HaveCount(3); + items.Count(item => item is ThreadMailItemViewModel).Should().Be(2); + items.Count(item => item is MailItemViewModel).Should().Be(1); + + var threadItems = items.OfType().ToList(); + threadItems.Should().Contain(item => item.ThreadId == "thread-a" && item.EmailCount == 2); + threadItems.Should().Contain(item => item.ThreadId == "thread-c" && item.EmailCount == 2); + } + + private static WinoMailCollection CreateCollection() => new() + { + CoreDispatcher = new ImmediateDispatcher() + }; + + private static List FlattenItems(WinoMailCollection collection) + { + var items = new List(); + + foreach (var group in collection.MailItems) + { + foreach (var item in group) + { + items.Add(item); + } + } + + return items; + } + + private static MailCopy CreateMailCopy(string threadId, DateTime? creationDate = null) + => new() + { + UniqueId = Guid.NewGuid(), + ThreadId = threadId, + CreationDate = creationDate ?? DateTime.UtcNow, + FromName = "Sender", + FromAddress = "sender@wino.dev", + Subject = "Subject", + PreviewText = "Preview", + FileId = Guid.NewGuid(), + FolderId = Guid.NewGuid() + }; + + private sealed class ImmediateDispatcher : IDispatcher + { + public Task ExecuteOnUIThread(Action action) + { + action(); + return Task.CompletedTask; + } + } +} diff --git a/Wino.Mail.ViewModels.Tests/GlobalUsings.cs b/Wino.Mail.ViewModels.Tests/GlobalUsings.cs new file mode 100644 index 00000000..29ee885e --- /dev/null +++ b/Wino.Mail.ViewModels.Tests/GlobalUsings.cs @@ -0,0 +1,4 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading.Tasks; diff --git a/Wino.Mail.ViewModels.Tests/Wino.Mail.ViewModels.Tests.csproj b/Wino.Mail.ViewModels.Tests/Wino.Mail.ViewModels.Tests.csproj new file mode 100644 index 00000000..1e3a4ad6 --- /dev/null +++ b/Wino.Mail.ViewModels.Tests/Wino.Mail.ViewModels.Tests.csproj @@ -0,0 +1,24 @@ + + + net10.0 + enable + enable + false + true + x86;x64;arm64 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/WinoMail.slnx b/WinoMail.slnx index edc5bae8..9a2893e3 100644 --- a/WinoMail.slnx +++ b/WinoMail.slnx @@ -61,6 +61,11 @@ + + + + +