|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Test;
public class TagHelperCollectionTest
{
private static TagHelperDescriptor CreateTagHelper(string name, string assemblyName = "TestAssembly")
{
var builder = TagHelperDescriptorBuilder.Create(name, assemblyName);
builder.TypeName = name;
builder.TagMatchingRule(rule => rule.TagName = name.ToLowerInvariant());
return builder.Build();
}
private static TagHelperDescriptor[] CreateTestTagHelpers(int count, int startIndex = 0)
{
var result = new TagHelperDescriptor[count];
for (var i = 0; i < count; i++)
{
result[i] = CreateTagHelper($"TagHelper{startIndex + i}");
}
return result;
}
[Fact]
public void Empty_ReturnsEmptyCollection()
{
// Act
var collection = TagHelperCollection.Empty;
// Assert
Assert.NotNull(collection);
Assert.Empty(collection);
Assert.True(collection.IsEmpty);
Assert.Equal(-1, collection.IndexOf(CreateTagHelper("Test")));
Assert.False(collection.Contains(CreateTagHelper("Test")));
}
[Fact]
public void Empty_Enumerator_IsEmpty()
{
// Arrange
var collection = TagHelperCollection.Empty;
// Act & Assert
Assert.Empty(collection);
var enumerator = collection.GetEnumerator();
Assert.False(enumerator.MoveNext());
}
[Fact]
public void Empty_CopyTo_DoesNothing()
{
// Arrange
var collection = TagHelperCollection.Empty;
var destination = new TagHelperDescriptor[1];
// Act
collection.CopyTo(destination);
// Assert
Assert.Null(destination[0]);
}
[Fact]
public void Empty_Indexer_ThrowsIndexOutOfRangeException()
{
// Arrange
var collection = TagHelperCollection.Empty;
// Act & Assert
Assert.Throws<IndexOutOfRangeException>(() => collection[0]);
Assert.Throws<IndexOutOfRangeException>(() => collection[-1]);
}
[Fact]
public void Create_EmptyImmutableArray_ReturnsEmpty()
{
// Act
var empty = ImmutableArray<TagHelperDescriptor>.Empty;
var collection = TagHelperCollection.Create(empty);
// Assert
Assert.Same(TagHelperCollection.Empty, collection);
}
[Fact]
public void Create_SingleItemImmutableArray_ReturnsSingleItemCollection()
{
// Arrange
var tagHelper = CreateTagHelper("Test");
var array = ImmutableArray.Create(tagHelper);
// Act
var collection = TagHelperCollection.Create(array);
// Assert
Assert.Single(collection);
Assert.False(collection.IsEmpty);
Assert.Same(tagHelper, collection[0]);
Assert.Equal(0, collection.IndexOf(tagHelper));
Assert.True(collection.Contains(tagHelper));
}
[Fact]
public void Create_MultipleItemsImmutableArray_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var array = ImmutableArray.Create(tagHelpers);
// Act
var collection = TagHelperCollection.Create(array);
// Assert
Assert.Equal(3, collection.Count);
Assert.False(collection.IsEmpty);
for (var i = 0; i < tagHelpers.Length; i++)
{
Assert.Same(tagHelpers[i], collection[i]);
Assert.Equal(i, collection.IndexOf(tagHelpers[i]));
Assert.True(collection.Contains(tagHelpers[i]));
}
}
[Fact]
public void Create_ImmutableArrayWithDuplicates_RemovesDuplicates()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var array = ImmutableArray.Create(tagHelper1, tagHelper2, tagHelper1);
// Act
var collection = TagHelperCollection.Create(array);
// Assert
Assert.SameItems([tagHelper1, tagHelper2], collection);
}
[Fact]
public void Create_EmptyArray_ReturnsEmpty()
{
// Arrange
var array = Array.Empty<TagHelperDescriptor>();
// Act
var collection = TagHelperCollection.Create(array);
// Assert
Assert.Same(TagHelperCollection.Empty, collection);
}
[Fact]
public void Create_SingleItemArray_ReturnsSingleItemCollection()
{
// Arrange
var tagHelper = CreateTagHelper("Test");
var array = new[] { tagHelper };
// Act
var collection = TagHelperCollection.Create(array);
// Assert
Assert.Single(collection);
Assert.Same(tagHelper, collection[0]);
}
[Fact]
public void Create_MultipleItemArray_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(4);
// Act
var collection = TagHelperCollection.Create(tagHelpers);
// Assert
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_ArrayWithDuplicates_RemovesDuplicates()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var array = new[] { tagHelper1, tagHelper2, tagHelper1 };
// Act
var collection = TagHelperCollection.Create(array);
// Assert
Assert.SameItems([tagHelper1, tagHelper2], collection);
}
[Fact]
public void Create_EmptyCollectionExpression_ReturnsEmpty()
{
// Act
TagHelperCollection collection = [];
// Assert
Assert.Same(TagHelperCollection.Empty, collection);
}
[Fact]
public void Create_SingleItemCollectionExpression_ReturnsSingleItemCollection()
{
// Arrange
var tagHelper = CreateTagHelper("Test");
// Act
TagHelperCollection collection = [tagHelper];
// Assert
Assert.Single(collection);
Assert.False(collection.IsEmpty);
Assert.Same(tagHelper, collection[0]);
Assert.Equal(0, collection.IndexOf(tagHelper));
Assert.True(collection.Contains(tagHelper));
}
[Fact]
public void Create_MultipleItemsCollectionExpression_CreatesCollection()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
// Act
TagHelperCollection collection = [tagHelper1, tagHelper2, tagHelper3];
// Assert
Assert.Equal(3, collection.Count);
Assert.False(collection.IsEmpty);
Assert.Same(tagHelper1, collection[0]);
Assert.Same(tagHelper2, collection[1]);
Assert.Same(tagHelper3, collection[2]);
Assert.Equal(0, collection.IndexOf(tagHelper1));
Assert.Equal(1, collection.IndexOf(tagHelper2));
Assert.Equal(2, collection.IndexOf(tagHelper3));
Assert.True(collection.Contains(tagHelper1));
Assert.True(collection.Contains(tagHelper2));
Assert.True(collection.Contains(tagHelper3));
}
[Fact]
public void Create_CollectionExpressionWithDuplicates_RemovesDuplicates()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
// Act
TagHelperCollection collection = [tagHelper1, tagHelper2, tagHelper1];
// Assert
Assert.SameItems([tagHelper1, tagHelper2], collection);
}
[Fact]
public void Create_CollectionExpressionWithSpreadOperator_CreatesCollection()
{
// Arrange
var firstBatch = CreateTestTagHelpers(2);
var additionalTagHelper = CreateTagHelper("Additional");
var lastBatch = CreateTestTagHelpers(2).Skip(2).ToArray(); // Get different helpers
// Act
TagHelperCollection collection = [.. firstBatch, additionalTagHelper, .. lastBatch];
// Assert
Assert.Equal(3, collection.Count); // 2 from first batch + 1 additional (lastBatch would be empty in this case)
Assert.Same(firstBatch[0], collection[0]);
Assert.Same(firstBatch[1], collection[1]);
Assert.Same(additionalTagHelper, collection[2]);
}
[Fact]
public void Create_CollectionExpressionFromExistingArray_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(4);
// Act
TagHelperCollection collection = [.. tagHelpers];
// Assert
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_CollectionExpressionMixedSources_CreatesCollection()
{
// Arrange
var singleHelper = CreateTagHelper("Single");
var tagHelpers = CreateTestTagHelpers(3);
TagHelperDescriptor[] arrayHelpers = [tagHelpers[0], tagHelpers[1]];
List<TagHelperDescriptor> listHelpers = [tagHelpers[2]];
// Act
TagHelperCollection collection = [singleHelper, .. arrayHelpers, .. listHelpers];
// Assert
Assert.Equal(4, collection.Count);
Assert.Same(singleHelper, collection[0]);
Assert.Same(arrayHelpers[0], collection[1]);
Assert.Same(arrayHelpers[1], collection[2]);
Assert.Same(listHelpers[0], collection[3]);
}
[Fact]
public void Create_IEnumerableEmpty_ReturnsEmpty()
{
// Arrange
var items = new List<TagHelperDescriptor>();
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Same(TagHelperCollection.Empty, collection);
}
[Fact]
public void Create_IEnumerableEmptyEnumerable_ReturnsEmpty()
{
// Arrange - Use LINQ to create an enumerable without known count
var items = new[] { CreateTagHelper("Test") }.Where(x => false);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Same(TagHelperCollection.Empty, collection);
}
[Fact]
public void Create_IEnumerableSingleItem_ReturnsSingleItemCollection()
{
// Arrange
var tagHelper = CreateTagHelper("Test");
var items = new List<TagHelperDescriptor> { tagHelper };
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Single(collection);
Assert.False(collection.IsEmpty);
Assert.Same(tagHelper, collection[0]);
Assert.Equal(0, collection.IndexOf(tagHelper));
Assert.True(collection.Contains(tagHelper));
}
[Fact]
public void Create_IEnumerableSingleItemEnumerable_ReturnsSingleItemCollection()
{
// Arrange - Use LINQ to create an enumerable without known count
var tagHelper = CreateTagHelper("Test");
var items = new[] { tagHelper, CreateTagHelper("Other") }.Where(x => x == tagHelper);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Single(collection);
Assert.Same(tagHelper, collection[0]);
}
[Fact]
public void Create_IEnumerableMultipleItems_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(4);
var items = new List<TagHelperDescriptor>(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(4, collection.Count);
Assert.False(collection.IsEmpty);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableMultipleItemsEnumerable_CreatesCollection()
{
// Arrange - Use LINQ to create an enumerable without known count
var tagHelpers = CreateTestTagHelpers(4);
var items = tagHelpers.Where(x => true); // Forces enumerable without known count
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(4, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableWithDuplicates_RemovesDuplicates()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var items = new List<TagHelperDescriptor> { tagHelper1, tagHelper2, tagHelper1, tagHelper2 };
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(2, collection.Count);
Assert.SameItems([tagHelper1, tagHelper2], collection);
}
[Fact]
public void Create_IEnumerableEnumerableWithDuplicates_RemovesDuplicates()
{
// Arrange - Use LINQ to create an enumerable without known count
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var baseItems = new[] { tagHelper1, tagHelper2, tagHelper1, tagHelper2 };
var items = baseItems.Where(x => true); // Forces enumerable without known count
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(2, collection.Count);
Assert.SameItems([tagHelper1, tagHelper2], collection);
}
[Fact]
public void Create_IEnumerableHashSet_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var items = new HashSet<TagHelperDescriptor>(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(3, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableQueue_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var items = new Queue<TagHelperDescriptor>(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(3, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableStack_CreatesCollection()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var items = new Stack<TagHelperDescriptor>();
items.Push(tagHelper1);
items.Push(tagHelper2);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(2, collection.Count);
// Note: Stack reverses order, so tagHelper2 comes first
Assert.Same(tagHelper2, collection[0]);
Assert.Same(tagHelper1, collection[1]);
}
[Fact]
public void Create_IEnumerableLinkedList_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var items = new LinkedList<TagHelperDescriptor>(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(3, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableCustomCollection_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var items = new CustomCollection(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(3, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableCustomEnumerable_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var items = new CustomEnumerable(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(3, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableLargeCollection_WorksCorrectly()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(1000);
var items = new List<TagHelperDescriptor>(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(1000, collection.Count);
Assert.Same(tagHelpers[0], collection[0]);
Assert.Same(tagHelpers[999], collection[999]);
}
[Fact]
public void Create_IEnumerableLargeEnumerable_WorksCorrectly()
{
// Arrange - Use LINQ to create an enumerable without known count
var tagHelpers = CreateTestTagHelpers(1000);
var items = tagHelpers.Where(x => true);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(1000, collection.Count);
Assert.Same(tagHelpers[0], collection[0]);
Assert.Same(tagHelpers[999], collection[999]);
}
[Fact]
public void Create_IEnumerableReadOnlyCollection_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
IReadOnlyCollection<TagHelperDescriptor> items = tagHelpers;
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(3, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Create_IEnumerableYieldReturn_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
static IEnumerable<TagHelperDescriptor> YieldItems(TagHelperDescriptor[] items)
{
foreach (var item in items)
{
yield return item;
}
}
var items = YieldItems(tagHelpers);
// Act
var collection = TagHelperCollection.Create(items);
// Assert
Assert.Equal(3, collection.Count);
Assert.SameItems(tagHelpers, collection);
}
// Helper classes for testing
private sealed class CustomCollection(IEnumerable<TagHelperDescriptor> items) : ICollection<TagHelperDescriptor>
{
private readonly List<TagHelperDescriptor> _items = [.. items];
public int Count => _items.Count;
public bool IsReadOnly => true;
public void Add(TagHelperDescriptor item) => throw new NotSupportedException();
public void Clear() => throw new NotSupportedException();
public bool Contains(TagHelperDescriptor item) => _items.Contains(item);
public void CopyTo(TagHelperDescriptor[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
public IEnumerator<TagHelperDescriptor> GetEnumerator() => _items.GetEnumerator();
public bool Remove(TagHelperDescriptor item) => throw new NotSupportedException();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
private sealed class CustomEnumerable(IEnumerable<TagHelperDescriptor> items) : IEnumerable<TagHelperDescriptor>
{
private readonly List<TagHelperDescriptor> _items = [.. items];
public IEnumerator<TagHelperDescriptor> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Fact]
public void ConcurrentAccess_MultipleThreads_DoesNotThrow()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(100);
var collection = TagHelperCollection.Create(tagHelpers);
var exceptions = new ConcurrentBag<Exception>();
// Act
Parallel.For(0, 100, i =>
{
try
{
_ = collection[i % collection.Count];
_ = collection.IndexOf(tagHelpers[i % tagHelpers.Length]);
_ = collection.Contains(tagHelpers[i % tagHelpers.Length]);
foreach (var item in collection) { /* enumerate */ }
}
catch (Exception ex)
{
exceptions.Add(ex);
}
});
// Assert
Assert.Empty(exceptions);
}
[Fact]
public void Build_EmptyBuilder_ReturnsEmpty()
{
// Act
var collection = TagHelperCollection.Build("test", (ref builder, state) =>
{
// Don't add anything
});
// Assert
Assert.Same(TagHelperCollection.Empty, collection);
}
[Fact]
public void Build_WithItems_CreatesCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
// Act
var collection = TagHelperCollection.Build(tagHelpers, (ref builder, items) =>
{
foreach (var item in items)
{
builder.Add(item);
}
});
// Assert
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Build_WithDuplicates_RemovesDuplicates()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
// Act
var collection = TagHelperCollection.Build((tagHelper1, tagHelper2), (ref builder, items) =>
{
builder.Add(items.tagHelper1);
builder.Add(items.tagHelper2);
builder.Add(items.tagHelper1); // Duplicate
});
// Assert
Assert.SameItems([tagHelper1, tagHelper2], collection);
}
[Fact]
public void Equals_SameInstance_ReturnsTrue()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(2));
// Act & Assert
Assert.True(collection.Equals(collection));
Assert.True(collection.Equals((object)collection));
}
[Fact]
public void Equals_DifferentInstanceSameContent_ReturnsTrue()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(2);
var collection1 = TagHelperCollection.Create(tagHelpers);
var collection2 = TagHelperCollection.Create(tagHelpers);
// Act & Assert
Assert.True(collection1.Equals(collection2));
Assert.True(collection1.Equals((object)collection2));
}
[Fact]
public void Equals_DifferentContent_ReturnsFalse()
{
// Arrange
var collection1 = TagHelperCollection.Create(CreateTestTagHelpers(2));
var collection2 = TagHelperCollection.Create(CreateTestTagHelpers(3));
// Act & Assert
Assert.False(collection1.Equals(collection2));
Assert.False(collection1.Equals((object)collection2));
}
[Fact]
public void Equals_Null_ReturnsFalse()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(2));
// Act & Assert
Assert.False(collection.Equals(null));
Assert.False(collection.Equals((object?)null));
}
[Fact]
public void Equals_DifferentType_ReturnsFalse()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(2));
// Act & Assert
Assert.False(collection.Equals("not a collection"));
}
[Fact]
public void Equals_ArrayCreatedVsMergedCollection_SameContent_ReturnsTrue()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
var array = new[] { tagHelper1, tagHelper2, tagHelper3 };
// Create collection directly from array
var arrayCollection = TagHelperCollection.Create(array);
// Create collection by merging two collections with the same content
TagHelperCollection firstPart = [tagHelper1];
TagHelperCollection secondPart = [tagHelper2, tagHelper3];
var mergedCollection = TagHelperCollection.Merge(firstPart, secondPart);
// Act & Assert
Assert.Equal(arrayCollection.Count, mergedCollection.Count);
Assert.True(arrayCollection.Contains(tagHelper1));
Assert.True(arrayCollection.Contains(tagHelper2));
Assert.True(arrayCollection.Contains(tagHelper3));
Assert.True(mergedCollection.Contains(tagHelper1));
Assert.True(mergedCollection.Contains(tagHelper2));
Assert.True(mergedCollection.Contains(tagHelper3));
// Verify same order
Assert.Same(tagHelper1, arrayCollection[0]);
Assert.Same(tagHelper2, arrayCollection[1]);
Assert.Same(tagHelper3, arrayCollection[2]);
Assert.Same(tagHelper1, mergedCollection[0]);
Assert.Same(tagHelper2, mergedCollection[1]);
Assert.Same(tagHelper3, mergedCollection[2]);
// This should pass if equality works correctly regardless of construction method
Assert.True(arrayCollection.Equals(mergedCollection));
Assert.True(mergedCollection.Equals(arrayCollection));
Assert.Equal(arrayCollection.GetHashCode(), mergedCollection.GetHashCode());
}
[Fact]
public void GetHashCode_SameContent_ReturnsSameHashCode()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(2);
var collection1 = TagHelperCollection.Create(tagHelpers);
var collection2 = TagHelperCollection.Create(tagHelpers);
// Act & Assert
Assert.Equal(collection1.GetHashCode(), collection2.GetHashCode());
}
[Fact]
public void GetHashCode_DifferentContent_ReturnsDifferentHashCode()
{
// Arrange
var collection1 = TagHelperCollection.Create(CreateTestTagHelpers(2));
var collection2 = TagHelperCollection.Create(CreateTestTagHelpers(3));
// Act & Assert
Assert.NotEqual(collection1.GetHashCode(), collection2.GetHashCode());
}
[Fact]
public void GetHashCode_DifferentOrder_ReturnsDifferentChecksum()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(2);
var collection1 = TagHelperCollection.Create([tagHelpers[0], tagHelpers[1]]);
var collection2 = TagHelperCollection.Create([tagHelpers[1], tagHelpers[0]]);
// Act & Assert
Assert.NotEqual(collection1.GetHashCode(), collection2.GetHashCode());
}
[Fact]
public void Checksum_DifferentOrderSameContent_DifferentChecksums()
{
// Arrange
var tagHelper1 = CreateTagHelper("A");
var tagHelper2 = CreateTagHelper("B");
var collection1 = TagHelperCollection.Create([tagHelper1, tagHelper2]);
var collection2 = TagHelperCollection.Create([tagHelper2, tagHelper1]);
// Act & Assert - Order matters for checksums
Assert.NotEqual(collection1.GetHashCode(), collection2.GetHashCode());
Assert.False(collection1.Equals(collection2));
}
[Fact]
public void Checksum_IdenticalContent_IdenticalChecksums()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(5);
var collection1 = TagHelperCollection.Create(tagHelpers);
var collection2 = TagHelperCollection.Create(tagHelpers);
// Act & Assert - Same content should have same checksum
Assert.Equal(collection1.GetHashCode(), collection2.GetHashCode());
Assert.True(collection1.Equals(collection2));
}
[Fact]
public void GetEnumerator_Generic_EnumeratesAllItems()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
// Act
var enumeratedItems = new List<TagHelperDescriptor>();
foreach (var item in collection)
{
enumeratedItems.Add(item);
}
// Assert
Assert.SameItems(tagHelpers, enumeratedItems);
}
[Fact]
public void GetEnumerator_NonGeneric_EnumeratesAllItems()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
// Act
var enumeratedItems = new List<TagHelperDescriptor>();
var enumerator = ((IEnumerable)collection).GetEnumerator();
while (enumerator.MoveNext())
{
enumeratedItems.Add((TagHelperDescriptor)enumerator.Current);
}
// Assert
Assert.SameItems(tagHelpers, enumeratedItems);
}
[Fact]
public void Enumerator_Current_BeforeMoveNext_ThrowsInvalidOperation()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(1));
var enumerator = collection.GetEnumerator();
// Act & Assert
Assert.Throws<InvalidOperationException>(() => _ = enumerator.Current);
}
[Fact]
public void CopyTo_ValidDestination_CopiesAllItems()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
var destination = new TagHelperDescriptor[5];
// Act
collection.CopyTo(destination);
// Assert
Assert.Same(tagHelpers[0], destination[0]);
Assert.Same(tagHelpers[1], destination[1]);
Assert.Same(tagHelpers[2], destination[2]);
Assert.Null(destination[3]);
Assert.Null(destination[4]);
}
[Fact]
public void CopyTo_DestinationTooShort_ThrowsArgumentException()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
var destination = new TagHelperDescriptor[2];
// Act & Assert
Assert.Throws<ArgumentException>(() => collection.CopyTo(destination));
}
[Fact]
public void Indexer_ValidIndex_ReturnsCorrectItem()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
// Act & Assert
Assert.Equal(tagHelpers.Length, collection.Count);
for (var i = 0; i < tagHelpers.Length; i++)
{
Assert.Same(tagHelpers[i], collection[i]);
}
}
[Fact]
public void Indexer_InvalidIndex_ThrowsArgumentOutOfRangeException()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(3));
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => collection[-1]);
Assert.Throws<ArgumentOutOfRangeException>(() => collection[3]);
Assert.Throws<ArgumentOutOfRangeException>(() => collection[10]);
}
[Fact]
public void IndexOf_ExistingItem_ReturnsCorrectIndex()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
// Act & Assert
for (var i = 0; i < tagHelpers.Length; i++)
{
Assert.Equal(i, collection.IndexOf(tagHelpers[i]));
}
}
[Fact]
public void IndexOf_NonExistingItem_ReturnsMinusOne()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
var nonExistingItem = CreateTagHelper("NonExisting");
// Act
var index = collection.IndexOf(nonExistingItem);
// Assert
Assert.Equal(-1, index);
}
[Fact]
public void IndexOf_LargeCollection_UsesLookupTable()
{
// Arrange - Test above the 8-item threshold
var tagHelpers = CreateTestTagHelpers(100);
var collection = TagHelperCollection.Create(tagHelpers);
var searchItem = tagHelpers[50];
// Act & Assert - Should find the item (testing functionality, not speed)
Assert.Equal(50, collection.IndexOf(searchItem));
}
[Fact]
public void IndexOf_SmallCollection_UsesLinearSearch()
{
// Arrange - Test below the 8-item threshold
var tagHelpers = CreateTestTagHelpers(5);
var collection = TagHelperCollection.Create(tagHelpers);
var searchItem = tagHelpers[3];
// Act & Assert - Should find the item
Assert.Equal(3, collection.IndexOf(searchItem));
}
[Fact]
public void Contains_ExistingItem_ReturnsTrue()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
// Act & Assert
foreach (var tagHelper in tagHelpers)
{
Assert.True(collection.Contains(tagHelper));
}
}
[Fact]
public void Contains_NonExistingItem_ReturnsFalse()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
var nonExistingItem = CreateTagHelper("NonExisting");
// Act
var contains = collection.Contains(nonExistingItem);
// Assert
Assert.False(contains);
}
[Fact]
public void Create_VeryLargeCollection_WorksCorrectly()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(1000);
// Act
var collection = TagHelperCollection.Create(tagHelpers);
// Assert
Assert.Equal(1000, collection.Count);
Assert.Same(tagHelpers[0], collection[0]);
Assert.Same(tagHelpers[999], collection[999]);
Assert.Equal(0, collection.IndexOf(tagHelpers[0]));
Assert.Equal(999, collection.IndexOf(tagHelpers[999]));
}
[Fact]
public void IsEmpty_EmptyCollection_ReturnsTrue()
{
// Assert
Assert.True(TagHelperCollection.Empty.IsEmpty);
}
[Fact]
public void IsEmpty_NonEmptyCollection_ReturnsFalse()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(1));
// Assert
Assert.False(collection.IsEmpty);
}
[Fact]
public void Builder_Empty_IsEmptyAndHasZeroCount()
{
// Arrange & Act
using var builder = new TagHelperCollection.Builder();
// Assert
Assert.True(builder.IsEmpty);
Assert.Empty(builder);
Assert.False(builder.IsReadOnly);
}
[Fact]
public void Builder_AddSingleItem_WorksCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
// Act
var added = builder.Add(tagHelper);
// Assert
Assert.True(added);
Assert.False(builder.IsEmpty);
var item = Assert.Single(builder);
Assert.Same(tagHelper, item);
}
[Fact]
public void Builder_AddDuplicateSingleItem_ReturnsFalse()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
// Act
var firstAdd = builder.Add(tagHelper);
var secondAdd = builder.Add(tagHelper);
// Assert
Assert.True(firstAdd);
Assert.False(secondAdd);
var item = Assert.Single(builder);
Assert.Same(tagHelper, item);
}
[Fact]
public void Builder_AddMultipleItems_WorksCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
// Act
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Assert
Assert.False(builder.IsEmpty);
Assert.Equal(3, builder.Count);
for (var i = 0; i < tagHelpers.Length; i++)
{
Assert.Same(tagHelpers[i], builder[i]);
}
}
[Fact]
public void Builder_AddMultipleItemsWithDuplicates_DeduplicatesCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
// Act
var add1 = builder.Add(tagHelper1);
var add2 = builder.Add(tagHelper2);
var add3 = builder.Add(tagHelper1); // Duplicate
// Assert
Assert.True(add1);
Assert.True(add2);
Assert.False(add3); // Should return false for duplicate
Assert.Equal(2, builder.Count);
Assert.Same(tagHelper1, builder[0]);
Assert.Same(tagHelper2, builder[1]);
}
[Fact]
public void Builder_IndexerValidIndex_ReturnsCorrectItem()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act & Assert
for (var i = 0; i < tagHelpers.Length; i++)
{
Assert.Same(tagHelpers[i], builder[i]);
}
}
[Fact]
public void Builder_IndexerNegativeIndex_ThrowsArgumentOutOfRangeException()
{
// Arrange
using var builder = new TagHelperCollection.Builder
{
CreateTagHelper("Test")
};
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => builder[-1]);
}
[Fact]
public void Builder_IndexerOutOfRange_ThrowsArgumentOutOfRangeException()
{
// Arrange
using var builder = new TagHelperCollection.Builder
{
CreateTagHelper("Test")
};
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => builder[1]);
Assert.Throws<ArgumentOutOfRangeException>(() => builder[10]);
}
[Fact]
public void Builder_IndexerEmptyBuilder_ThrowsArgumentOutOfRangeException()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => builder[0]);
}
[Fact]
public void Builder_ContainsSingleItem_ReturnsCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
var otherHelper = CreateTagHelper("Other");
builder.Add(tagHelper);
// Act & Assert
Assert.Contains(tagHelper, builder);
Assert.DoesNotContain(otherHelper, builder);
}
[Fact]
public void Builder_ContainsMultipleItems_ReturnsCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
var otherHelper = CreateTagHelper("Other");
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act & Assert
foreach (var tagHelper in tagHelpers)
{
Assert.Contains(tagHelper, builder);
}
Assert.DoesNotContain(otherHelper, builder);
}
[Fact]
public void Builder_ContainsEmptyBuilder_ReturnsFalse()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
// Act & Assert
Assert.DoesNotContain(tagHelper, builder);
}
[Fact]
public void Builder_ClearSingleItem_MakesEmpty()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
builder.Add(tagHelper);
// Act
builder.Clear();
// Assert
Assert.True(builder.IsEmpty);
Assert.Empty(builder);
}
[Fact]
public void Builder_ClearMultipleItems_MakesEmpty()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act
builder.Clear();
// Assert
Assert.True(builder.IsEmpty);
Assert.Empty(builder);
}
[Fact]
public void Builder_ClearEmptyBuilder_RemainsEmpty()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
// Act
builder.Clear();
// Assert
Assert.True(builder.IsEmpty);
Assert.Empty(builder);
}
[Fact]
public void Builder_RemoveSingleItemExists_ReturnsTrue()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
builder.Add(tagHelper);
// Act
var removed = builder.Remove(tagHelper);
// Assert
Assert.True(removed);
Assert.True(builder.IsEmpty);
Assert.Empty(builder);
}
[Fact]
public void Builder_RemoveSingleItemNotExists_ReturnsFalse()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
var otherHelper = CreateTagHelper("Other");
builder.Add(tagHelper);
// Act
var removed = builder.Remove(otherHelper);
// Assert
Assert.False(removed);
var item = Assert.Single(builder);
Assert.Same(tagHelper, item);
}
[Fact]
public void Builder_RemoveFromMultipleItems_WorksCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act
var removed = builder.Remove(tagHelpers[1]);
// Assert
Assert.True(removed);
Assert.Equal(2, builder.Count);
Assert.Same(tagHelpers[0], builder[0]);
Assert.Same(tagHelpers[2], builder[1]);
Assert.DoesNotContain(tagHelpers[1], builder);
}
[Fact]
public void Builder_RemoveFromEmptyBuilder_ReturnsFalse()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
// Act
var removed = builder.Remove(tagHelper);
// Assert
Assert.False(removed);
Assert.True(builder.IsEmpty);
}
[Fact]
public void Builder_CopyToSingleItem_CopiesCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
builder.Add(tagHelper);
var destination = new TagHelperDescriptor[3];
// Act
builder.CopyTo(destination, 1);
// Assert
Assert.Null(destination[0]);
Assert.Same(tagHelper, destination[1]);
Assert.Null(destination[2]);
}
[Fact]
public void Builder_CopyToMultipleItems_CopiesCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
var destination = new TagHelperDescriptor[5];
// Act
builder.CopyTo(destination, 1);
// Assert
Assert.Null(destination[0]);
Assert.Same(tagHelpers[0], destination[1]);
Assert.Same(tagHelpers[1], destination[2]);
Assert.Same(tagHelpers[2], destination[3]);
Assert.Null(destination[4]);
}
[Fact]
public void Builder_CopyToEmptyBuilder_DoesNothing()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var destination = new TagHelperDescriptor[3];
// Act
builder.CopyTo(destination, 1);
// Assert
Assert.All(destination, item => Assert.Null(item));
}
[Fact]
public void Builder_ToCollectionEmpty_ReturnsEmpty()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
// Act
var collection = builder.ToCollection();
// Assert
Assert.Same(TagHelperCollection.Empty, collection);
}
[Fact]
public void Builder_ToCollectionSingleItem_ReturnsSingleItemCollection()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
builder.Add(tagHelper);
// Act
var collection = builder.ToCollection();
// Assert
Assert.Single(collection);
Assert.Same(tagHelper, collection[0]);
}
[Fact]
public void Builder_ToCollectionMultipleItems_ReturnsCollection()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act
var collection = builder.ToCollection();
// Assert
Assert.SameItems(tagHelpers, collection);
}
[Fact]
public void Builder_ICollectionAdd_CallsAdd()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
ICollection<TagHelperDescriptor> collection = builder;
var tagHelper = CreateTagHelper("Test");
// Act
collection.Add(tagHelper);
// Assert
var item = Assert.Single(builder);
Assert.Same(tagHelper, item);
}
[Fact]
public void Builder_GetEnumeratorEmpty_IsEmpty()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
// Act
using var enumerator = builder.GetEnumerator();
// Assert
Assert.False(enumerator.MoveNext());
}
[Fact]
public void Builder_GetEnumeratorSingleItem_EnumeratesCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelper = CreateTagHelper("Test");
builder.Add(tagHelper);
// Act
var enumerated = new List<TagHelperDescriptor>();
using var enumerator = builder.GetEnumerator();
while (enumerator.MoveNext())
{
enumerated.Add(enumerator.Current);
}
// Assert
Assert.Single(enumerated);
Assert.Same(tagHelper, enumerated[0]);
}
[Fact]
public void Builder_GetEnumeratorMultipleItems_EnumeratesCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act
var enumerated = new List<TagHelperDescriptor>();
using var enumerator = builder.GetEnumerator();
while (enumerator.MoveNext())
{
enumerated.Add(enumerator.Current);
}
// Assert
Assert.SameItems(tagHelpers, enumerated);
}
[Fact]
public void Builder_GenericEnumerable_EnumeratesCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act
var enumerated = new List<TagHelperDescriptor>();
foreach (var item in (IEnumerable<TagHelperDescriptor>)builder)
{
enumerated.Add(item);
}
// Assert
Assert.SameItems(tagHelpers, enumerated);
}
[Fact]
public void Builder_NonGenericEnumerable_EnumeratesCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act
var enumerated = new List<TagHelperDescriptor>();
var enumerator = ((IEnumerable)builder).GetEnumerator();
while (enumerator.MoveNext())
{
enumerated.Add((TagHelperDescriptor)enumerator.Current);
}
// Assert
Assert.SameItems(tagHelpers, enumerated);
}
[Fact]
public void Builder_DisposeTwice_DoesNotThrow()
{
// Arrange
var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(3);
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Act & Assert - Should not throw
builder.Dispose();
builder.Dispose();
}
[Fact]
public void Builder_LargeNumberOfItems_WorksCorrectly()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(100);
// Act
foreach (var tagHelper in tagHelpers)
{
builder.Add(tagHelper);
}
// Assert
Assert.Equal(100, builder.Count);
Assert.False(builder.IsEmpty);
for (var i = 0; i < tagHelpers.Length; i++)
{
Assert.Same(tagHelpers[i], builder[i]);
Assert.Contains(tagHelpers[i], builder);
}
}
[Fact]
public void Builder_ModifyAfterToCollection_DoesNotAffectCollection()
{
// Arrange
using var builder = new TagHelperCollection.Builder();
var tagHelpers = CreateTestTagHelpers(2);
builder.Add(tagHelpers[0]);
var collection = builder.ToCollection();
// Act - Modify builder after creating collection
builder.Add(tagHelpers[1]);
// Assert - Original collection should be unchanged
Assert.Single(collection);
Assert.Same(tagHelpers[0], collection[0]);
// Builder should have new state
Assert.Equal(2, builder.Count);
Assert.Same(tagHelpers[0], builder[0]);
Assert.Same(tagHelpers[1], builder[1]);
}
[Fact]
public void Merge_BothEmpty_ReturnsEmpty()
{
// Act
var merged = TagHelperCollection.Merge(TagHelperCollection.Empty, TagHelperCollection.Empty);
// Assert
Assert.Same(TagHelperCollection.Empty, merged);
}
[Fact]
public void Merge_FirstEmpty_ReturnsSecond()
{
// Arrange
var second = TagHelperCollection.Create(CreateTestTagHelpers(2));
// Act
var merged = TagHelperCollection.Merge(TagHelperCollection.Empty, second);
// Assert
Assert.Same(second, merged);
}
[Fact]
public void Merge_SecondEmpty_ReturnsFirst()
{
// Arrange
var first = TagHelperCollection.Create(CreateTestTagHelpers(2));
// Act
var merged = TagHelperCollection.Merge(first, TagHelperCollection.Empty);
// Assert
Assert.Same(first, merged);
}
[Fact]
public void Merge_NoOverlapSingleItems_CreatesMergedCollection()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper2];
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert
Assert.Equal(2, merged.Count);
Assert.False(merged.IsEmpty);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
Assert.Equal(0, merged.IndexOf(tagHelper1));
Assert.Equal(1, merged.IndexOf(tagHelper2));
Assert.True(merged.Contains(tagHelper1));
Assert.True(merged.Contains(tagHelper2));
}
[Fact]
public void Merge_NoOverlapMultipleItems_CreatesMergedCollection()
{
// Arrange
var firstHelpers = CreateTestTagHelpers(3);
// Create different helpers for second collection
var secondHelper1 = CreateTagHelper("Second1");
var secondHelper2 = CreateTagHelper("Second2");
var first = TagHelperCollection.Create(firstHelpers);
TagHelperCollection second = [secondHelper1, secondHelper2];
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert
Assert.Equal(5, merged.Count);
Assert.False(merged.IsEmpty);
// Verify first collection items
for (var i = 0; i < firstHelpers.Length; i++)
{
Assert.Same(firstHelpers[i], merged[i]);
Assert.Equal(i, merged.IndexOf(firstHelpers[i]));
Assert.True(merged.Contains(firstHelpers[i]));
}
// Verify second collection items
Assert.Same(secondHelper1, merged[3]);
Assert.Same(secondHelper2, merged[4]);
Assert.Equal(3, merged.IndexOf(secondHelper1));
Assert.Equal(4, merged.IndexOf(secondHelper2));
Assert.True(merged.Contains(secondHelper1));
Assert.True(merged.Contains(secondHelper2));
}
[Fact]
public void Merge_WithOverlapSingleItems_DeduplicatesCorrectly()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper1, tagHelper2]; // Contains duplicate
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert
Assert.Equal(2, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
Assert.Equal(0, merged.IndexOf(tagHelper1));
Assert.Equal(1, merged.IndexOf(tagHelper2));
}
[Fact]
public void Merge_WithOverlapMultipleItems_DeduplicatesCorrectly()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
var tagHelper4 = CreateTagHelper("Test4");
TagHelperCollection first = [tagHelper1, tagHelper2];
TagHelperCollection second = [tagHelper2, tagHelper3, tagHelper4]; // tagHelper2 is duplicate
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert
Assert.Equal(4, merged.Count); // Should be deduplicated
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
Assert.Same(tagHelper3, merged[2]);
Assert.Same(tagHelper4, merged[3]);
// Verify all items are findable
Assert.Equal(0, merged.IndexOf(tagHelper1));
Assert.Equal(1, merged.IndexOf(tagHelper2));
Assert.Equal(2, merged.IndexOf(tagHelper3));
Assert.Equal(3, merged.IndexOf(tagHelper4));
}
[Fact]
public void Merge_CompleteOverlap_ReturnsDeduplicated()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var first = TagHelperCollection.Create(tagHelpers);
var second = TagHelperCollection.Create(tagHelpers); // Identical collections
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert
Assert.Equal(3, merged.Count); // Should not duplicate
Assert.SameItems(tagHelpers, merged);
}
[Fact]
public void Merge_MergedCollectionIndexer_WorksCorrectly()
{
// Arrange
var firstHelper = CreateTagHelper("First");
var secondHelper = CreateTagHelper("Second");
TagHelperCollection first = [firstHelper];
TagHelperCollection second = [secondHelper];
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert - Test indexer edge cases
Assert.Same(firstHelper, merged[0]);
Assert.Same(secondHelper, merged[1]);
Assert.Throws<ArgumentOutOfRangeException>(() => merged[-1]);
Assert.Throws<ArgumentOutOfRangeException>(() => merged[2]);
}
[Fact]
public void Merge_MergedCollectionCopyTo_WorksCorrectly()
{
// Arrange
var firstHelpers = CreateTestTagHelpers(2);
var secondHelper = CreateTagHelper("Second");
var first = TagHelperCollection.Create(firstHelpers);
TagHelperCollection second = [secondHelper];
var merged = TagHelperCollection.Merge(first, second);
var destination = new TagHelperDescriptor[5];
// Act
merged.CopyTo(destination);
// Assert
Assert.Same(firstHelpers[0], destination[0]);
Assert.Same(firstHelpers[1], destination[1]);
Assert.Same(secondHelper, destination[2]);
Assert.Null(destination[3]);
Assert.Null(destination[4]);
}
[Fact]
public void Merge_MergedCollectionCopyTo_DestinationTooShort_ThrowsArgumentException()
{
// Arrange
var firstHelper = CreateTagHelper("First");
var secondHelper = CreateTagHelper("Second");
TagHelperCollection first = [firstHelper];
TagHelperCollection second = [secondHelper];
var merged = TagHelperCollection.Merge(first, second);
var destination = new TagHelperDescriptor[1]; // Too short
// Act & Assert
Assert.Throws<ArgumentException>(() => merged.CopyTo(destination));
}
[Fact]
public void Merge_MergedCollectionIndexOf_NonExistingItem_ReturnsMinusOne()
{
// Arrange
var firstHelper = CreateTagHelper("First");
var secondHelper = CreateTagHelper("Second");
var nonExistingHelper = CreateTagHelper("NonExisting");
TagHelperCollection first = [firstHelper];
TagHelperCollection second = [secondHelper];
var merged = TagHelperCollection.Merge(first, second);
// Act
var index = merged.IndexOf(nonExistingHelper);
// Assert
Assert.Equal(-1, index);
}
[Fact]
public void Merge_MergedCollectionGetHashCode_DifferentForDifferentOrder()
{
// Arrange
var firstHelper = CreateTagHelper("First");
var secondHelper = CreateTagHelper("Second");
TagHelperCollection first = [firstHelper];
TagHelperCollection second = [secondHelper];
// Act
var merged1 = TagHelperCollection.Merge(first, second);
var merged2 = TagHelperCollection.Merge(second, first); // Different order
// Assert
Assert.NotEqual(merged1.GetHashCode(), merged2.GetHashCode());
}
[Fact]
public void Merge_MergedCollectionEquals_SameContent_ReturnsTrue()
{
// Arrange
var firstHelper = CreateTagHelper("First");
var secondHelper = CreateTagHelper("Second");
TagHelperCollection first = [firstHelper];
TagHelperCollection second = [secondHelper];
// Act
var merged1 = TagHelperCollection.Merge(first, second);
var merged2 = TagHelperCollection.Merge(first, second);
// Assert
Assert.True(merged1.Equals(merged2));
Assert.True(merged1.Equals((object)merged2));
}
[Fact]
public void Merge_MergedCollectionEnumeration_WorksCorrectly()
{
// Arrange
var firstHelpers = CreateTestTagHelpers(2);
var secondHelper = CreateTagHelper("Second");
var first = TagHelperCollection.Create(firstHelpers);
TagHelperCollection second = [secondHelper];
var merged = TagHelperCollection.Merge(first, second);
// Act
var enumerated = new List<TagHelperDescriptor>();
foreach (var item in merged)
{
enumerated.Add(item);
}
// Assert
Assert.Equal(3, enumerated.Count);
Assert.Same(firstHelpers[0], enumerated[0]);
Assert.Same(firstHelpers[1], enumerated[1]);
Assert.Same(secondHelper, enumerated[2]);
}
[Fact]
public void Merge_LargeCollections_WorksCorrectly()
{
// Arrange
var firstHelpers = CreateTestTagHelpers(500);
var secondHelpers = new TagHelperDescriptor[500];
for (var i = 0; i < 500; i++)
{
secondHelpers[i] = CreateTagHelper($"SecondHelper{i}");
}
var first = TagHelperCollection.Create(firstHelpers);
var second = TagHelperCollection.Create(secondHelpers);
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert
Assert.Equal(1000, merged.Count);
// Verify first collection items
for (var i = 0; i < 500; i++)
{
Assert.Same(firstHelpers[i], merged[i]);
}
// Verify second collection items
for (var i = 0; i < 500; i++)
{
Assert.Same(secondHelpers[i], merged[500 + i]);
}
}
[Fact]
public void Merge_WithPartialOverlap_DeduplicatesCorrectly()
{
// Arrange
var shared1 = CreateTagHelper("Shared1");
var shared2 = CreateTagHelper("Shared2");
var unique1 = CreateTagHelper("Unique1");
var unique2 = CreateTagHelper("Unique2");
var unique3 = CreateTagHelper("Unique3");
TagHelperCollection first = [unique1, shared1, unique2];
TagHelperCollection second = [shared1, unique3, shared2];
// Act
var merged = TagHelperCollection.Merge(first, second);
// Assert
Assert.Equal(5, merged.Count); // 3 + 3 - 1 (shared1 deduplicated)
// Verify order: first collection items, then unique items from second collection
Assert.Same(unique1, merged[0]);
Assert.Same(shared1, merged[1]); // From first collection
Assert.Same(unique2, merged[2]);
Assert.Same(unique3, merged[3]); // From second collection (shared1 already added)
Assert.Same(shared2, merged[4]); // From second collection
// Verify all items are findable
Assert.True(merged.Contains(unique1));
Assert.True(merged.Contains(shared1));
Assert.True(merged.Contains(unique2));
Assert.True(merged.Contains(unique3));
Assert.True(merged.Contains(shared2));
}
[Fact]
public void Merge_ImmutableArrayEmpty_ReturnsEmpty()
{
// Arrange
var collections = ImmutableArray<TagHelperCollection>.Empty;
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Same(TagHelperCollection.Empty, merged);
}
[Fact]
public void Merge_ImmutableArraySingleCollection_ReturnsSameCollection()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(3));
var collections = ImmutableArray.Create(collection);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Same(collection, merged);
}
[Fact]
public void Merge_ImmutableArrayTwoCollections_UsesOptimizedTwoCollectionMerge()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper2];
var collections = ImmutableArray.Create(first, second);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(2, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
}
[Fact]
public void Merge_ImmutableArrayThreeCollections_NoOverlap_CreatesEfficientMergedCollection()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper2];
TagHelperCollection third = [tagHelper3];
var collections = ImmutableArray.Create(first, second, third);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(3, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
Assert.Same(tagHelper3, merged[2]);
Assert.Equal(0, merged.IndexOf(tagHelper1));
Assert.Equal(1, merged.IndexOf(tagHelper2));
Assert.Equal(2, merged.IndexOf(tagHelper3));
Assert.True(merged.Contains(tagHelper1));
Assert.True(merged.Contains(tagHelper2));
Assert.True(merged.Contains(tagHelper3));
}
[Fact]
public void Merge_ImmutableArrayThreeCollections_WithDuplicates_DeduplicatesCorrectly()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper1, tagHelper2]; // tagHelper1 is duplicate
TagHelperCollection third = [tagHelper2, tagHelper3]; // tagHelper2 is duplicate
var collections = ImmutableArray.Create(first, second, third);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(3, merged.Count); // Should be deduplicated
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
Assert.Same(tagHelper3, merged[2]);
}
[Fact]
public void Merge_ImmutableArrayWithEmptyCollections_FiltersEmptyCollections()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
TagHelperCollection first = [tagHelper1];
var second = TagHelperCollection.Empty;
TagHelperCollection third = [tagHelper2];
var fourth = TagHelperCollection.Empty;
var collections = ImmutableArray.Create(first, second, third, fourth);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(2, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
}
[Fact]
public void Merge_ImmutableArrayMultipleCollections_LargeSet_WorksCorrectly()
{
// Arrange
var collections = ImmutableArray.CreateBuilder<TagHelperCollection>(10);
var allHelpers = new List<TagHelperDescriptor>();
for (var i = 0; i < 10; i++)
{
var helpers = new TagHelperDescriptor[5];
for (var j = 0; j < 5; j++)
{
helpers[j] = CreateTagHelper($"Collection{i}Helper{j}");
allHelpers.Add(helpers[j]);
}
collections.Add(TagHelperCollection.Create(helpers));
}
// Act
var merged = TagHelperCollection.Merge(collections.ToImmutable());
// Assert
Assert.Equal(50, merged.Count);
// Verify all items are present in correct order
for (var i = 0; i < 50; i++)
{
Assert.Same(allHelpers[i], merged[i]);
}
}
[Fact]
public void Merge_IEnumerableEmpty_ReturnsEmpty()
{
// Arrange
var collections = new List<TagHelperCollection>();
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Same(TagHelperCollection.Empty, merged);
}
[Fact]
public void Merge_IEnumerableSingleCollection_ReturnsSameCollection()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(3));
var collections = new List<TagHelperCollection> { collection };
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Same(collection, merged);
}
[Fact]
public void Merge_IEnumerableTwoCollections_UsesOptimizedPath()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper2];
var collections = new List<TagHelperCollection> { first, second };
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(2, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
}
[Fact]
public void Merge_IEnumerableMultipleCollections_WorksCorrectly()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper2];
TagHelperCollection third = [tagHelper3];
var collections = new List<TagHelperCollection> { first, second, third };
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(3, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
Assert.Same(tagHelper3, merged[2]);
}
[Fact]
public void Merge_IEnumerableWithoutKnownCount_WorksCorrectly()
{
// Arrange - Use LINQ to create an enumerable without known count
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper2];
var baseCollections = new[] { first, second };
var collections = baseCollections.Where(c => !c.IsEmpty);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(2, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
}
[Fact]
public void Merge_MultiCollectionMergedResult_SupportsIndexer()
{
// Arrange
var helpers = CreateTestTagHelpers(15).AsSpan();
var first = TagHelperCollection.Create(helpers[0..5]);
var second = TagHelperCollection.Create(helpers[5..10]);
var third = TagHelperCollection.Create(helpers[10..15]);
var collections = ImmutableArray.Create(first, second, third);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(15, merged.Count);
// Test indexer access
for (var i = 0; i < 15; i++)
{
Assert.Same(helpers[i], merged[i]);
}
// Test boundary conditions
Assert.Throws<ArgumentOutOfRangeException>(() => merged[-1]);
Assert.Throws<ArgumentOutOfRangeException>(() => merged[15]);
}
[Fact]
public void Merge_MultiCollectionMergedResult_SupportsIndexOf()
{
// Arrange
var helpers = CreateTestTagHelpers(9).AsSpan();
var first = TagHelperCollection.Create(helpers[0..3]);
var second = TagHelperCollection.Create(helpers[3..6]);
var third = TagHelperCollection.Create(helpers[6..9]);
var collections = ImmutableArray.Create(first, second, third);
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
for (var i = 0; i < 9; i++)
{
Assert.Equal(i, merged.IndexOf(helpers[i]));
}
var nonExistingHelper = CreateTagHelper("NonExisting");
Assert.Equal(-1, merged.IndexOf(nonExistingHelper));
}
[Fact]
public void Merge_MultiCollectionMergedResult_SupportsCopyTo()
{
// Arrange
var helpers = CreateTestTagHelpers(6).AsSpan();
var first = TagHelperCollection.Create(helpers[0..2]);
var second = TagHelperCollection.Create(helpers[2..4]);
var third = TagHelperCollection.Create(helpers[4..6]);
var collections = ImmutableArray.Create(first, second, third);
var merged = TagHelperCollection.Merge(collections);
var destination = new TagHelperDescriptor[8];
// Act
merged.CopyTo(destination);
// Assert
for (var i = 0; i < 6; i++)
{
Assert.Same(helpers[i], destination[i]);
}
Assert.Null(destination[6]);
Assert.Null(destination[7]);
}
[Fact]
public void Merge_MultiCollectionMergedResult_SupportsEnumeration()
{
// Arrange
var helpers = CreateTestTagHelpers(9).AsSpan();
var first = TagHelperCollection.Create(helpers[0..3]);
var second = TagHelperCollection.Create(helpers[3..6]);
var third = TagHelperCollection.Create(helpers[6..9]);
var collections = ImmutableArray.Create(first, second, third);
var merged = TagHelperCollection.Merge(collections);
// Act
var enumerated = new List<TagHelperDescriptor>();
foreach (var item in merged)
{
enumerated.Add(item);
}
// Assert
Assert.Equal(9, enumerated.Count);
for (var i = 0; i < 9; i++)
{
Assert.Same(helpers[i], enumerated[i]);
}
}
[Fact]
public void Merge_MultiCollectionMergedResult_GetHashCodeIsDeterministic()
{
// Arrange
var helpers = CreateTestTagHelpers(6).AsSpan();
var first = TagHelperCollection.Create(helpers[0..2]);
var second = TagHelperCollection.Create(helpers[2..4]);
var third = TagHelperCollection.Create(helpers[4..6]);
var collections = ImmutableArray.Create(first, second, third);
// Act
var merged1 = TagHelperCollection.Merge(collections);
var merged2 = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(merged1.GetHashCode(), merged2.GetHashCode());
}
[Fact]
public void Merge_MultiCollectionMergedResult_EqualityWorksCorrectly()
{
// Arrange
var helpers = CreateTestTagHelpers(6).AsSpan();
var first = TagHelperCollection.Create(helpers[0..2]);
var second = TagHelperCollection.Create(helpers[2..4]);
var third = TagHelperCollection.Create(helpers[4..6]);
var collections = ImmutableArray.Create(first, second, third);
// Act
var merged1 = TagHelperCollection.Merge(collections);
var merged2 = TagHelperCollection.Merge(collections);
var arrayCreated = TagHelperCollection.Create(helpers);
// Assert
Assert.True(merged1.Equals(merged2));
Assert.True(merged1.Equals(arrayCreated));
Assert.True(arrayCreated.Equals(merged1));
}
[Fact]
public void Merge_MultiCollectionNestedMerge_WorksCorrectly()
{
// Arrange
var helpers = CreateTestTagHelpers(12).AsSpan();
var first = TagHelperCollection.Create(helpers[0..3]);
var second = TagHelperCollection.Create(helpers[3..6]);
var third = TagHelperCollection.Create(helpers[6..9]);
var fourth = TagHelperCollection.Create(helpers[9..12]);
// Create nested merge structure
var merged1 = TagHelperCollection.Merge(first, second);
var merged2 = TagHelperCollection.Merge(third, fourth);
// Act
var finalMerged = TagHelperCollection.Merge(merged1, merged2);
// Assert
Assert.Equal(12, finalMerged.Count);
for (var i = 0; i < 12; i++)
{
Assert.Same(helpers[i], finalMerged[i]);
}
}
[Fact]
public void Merge_ReadOnlySpanEmpty_ReturnsEmpty()
{
// Arrange
ReadOnlySpan<TagHelperCollection> collections = [];
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Same(TagHelperCollection.Empty, merged);
}
[Fact]
public void Merge_ReadOnlySpanSingleCollection_ReturnsSameCollection()
{
// Arrange
var collection = TagHelperCollection.Create(CreateTestTagHelpers(3));
ReadOnlySpan<TagHelperCollection> collections = [collection];
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Same(collection, merged);
}
[Fact]
public void Merge_ReadOnlySpanMultipleCollections_WorksCorrectly()
{
// Arrange
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
TagHelperCollection first = [tagHelper1];
TagHelperCollection second = [tagHelper2];
TagHelperCollection third = [tagHelper3];
ReadOnlySpan<TagHelperCollection> collections = [first, second, third];
// Act
var merged = TagHelperCollection.Merge(collections);
// Assert
Assert.Equal(3, merged.Count);
Assert.Same(tagHelper1, merged[0]);
Assert.Same(tagHelper2, merged[1]);
Assert.Same(tagHelper3, merged[2]);
}
[Fact]
public void MergedCollection_EnumerationPerformance_AvoidsBinarySearchPerElement()
{
// Arrange - Create a scenario where enumeration would be slow if using indexer
var helpers1 = CreateTestTagHelpers(100);
var helpers2 = CreateTestTagHelpers(100, startIndex: 100);
var helpers3 = CreateTestTagHelpers(100, startIndex: 200);
var collection1 = TagHelperCollection.Create(helpers1);
var collection2 = TagHelperCollection.Create(helpers2);
var collection3 = TagHelperCollection.Create(helpers3);
var collections = ImmutableArray.Create(collection1, collection2, collection3);
var merged = TagHelperCollection.Merge(collections);
// Act - Enumerate the entire collection
var enumerated = new List<TagHelperDescriptor>();
foreach (var item in merged)
{
enumerated.Add(item);
}
// Assert - Verify all items are present in correct order
Assert.Equal(300, enumerated.Count);
for (var i = 0; i < 100; i++)
{
Assert.Same(helpers1[i], enumerated[i]);
}
for (var i = 0; i < 100; i++)
{
Assert.Same(helpers2[i], enumerated[100 + i]);
}
for (var i = 0; i < 100; i++)
{
Assert.Same(helpers3[i], enumerated[200 + i]);
}
}
[Fact]
public void MergedCollection_EnumerationState_HandlesSegmentTransitions()
{
// Arrange - Create collections of different sizes to test segment transitions
var helper1 = CreateTagHelper("Single");
var helpers2to4 = CreateTestTagHelpers(3, startIndex: 1);
var helpers5to9 = CreateTestTagHelpers(5, startIndex: 4);
TagHelperCollection collection1 = [helper1];
var collection2 = TagHelperCollection.Create(helpers2to4);
var collection3 = TagHelperCollection.Create(helpers5to9);
var merged = TagHelperCollection.Merge(ImmutableArray.Create(collection1, collection2, collection3));
// Act & Assert - Test enumeration crosses segment boundaries correctly
using var enumerator = merged.GetEnumerator();
// First segment (single item)
Assert.True(enumerator.MoveNext());
Assert.Same(helper1, enumerator.Current);
// Second segment (3 items)
for (var i = 0; i < 3; i++)
{
Assert.True(enumerator.MoveNext());
Assert.Same(helpers2to4[i], enumerator.Current);
}
// Third segment (5 items)
for (var i = 0; i < 5; i++)
{
Assert.True(enumerator.MoveNext());
Assert.Same(helpers5to9[i], enumerator.Current);
}
// Should be exhausted
Assert.False(enumerator.MoveNext());
}
[Fact]
public void MergedCollection_IndexerAccuracy_WithManySegments()
{
// Arrange - Create many small segments to stress-test binary search logic
var segments = new List<TagHelperCollection>();
var allHelpers = new List<TagHelperDescriptor>();
for (var i = 0; i < 20; i++)
{
var helpers = CreateTestTagHelpers(3, startIndex: i * 3);
segments.Add(TagHelperCollection.Create(helpers));
allHelpers.AddRange(helpers);
}
var merged = TagHelperCollection.Merge(segments.ToImmutableArray());
// Act & Assert - Test random access to various indices
Assert.Equal(60, merged.Count);
// Test accessing elements from different segments
var testIndices = new[] { 0, 1, 2, 5, 8, 15, 29, 35, 44, 59 };
foreach (var index in testIndices)
{
Assert.Same(allHelpers[index], merged[index]);
Assert.Equal(index, merged.IndexOf(allHelpers[index]));
}
}
[Fact]
public void MergedCollection_FindCollectionIndex_BinarySearchEdgeCases()
{
// Arrange - Create segments with specific sizes to test binary search edge cases
var segment1 = TagHelperCollection.Create(CreateTestTagHelpers(1)); // [0]
var segment2 = TagHelperCollection.Create(CreateTestTagHelpers(2, startIndex: 1)); // [1, 2]
var segment3 = TagHelperCollection.Create(CreateTestTagHelpers(3, startIndex: 3)); // [3, 4, 5]
var segment4 = TagHelperCollection.Create(CreateTestTagHelpers(1, startIndex: 6)); // [6]
var merged = TagHelperCollection.Merge(ImmutableArray.Create(segment1, segment2, segment3, segment4));
// Act & Assert - Test edge cases in binary search logic
// Start of segments
Assert.Equal(0, merged.IndexOf(CreateTagHelper("TagHelper0"))); // Start of segment 1
Assert.Equal(1, merged.IndexOf(CreateTagHelper("TagHelper1"))); // Start of segment 2
Assert.Equal(3, merged.IndexOf(CreateTagHelper("TagHelper3"))); // Start of segment 3
Assert.Equal(6, merged.IndexOf(CreateTagHelper("TagHelper6"))); // Start of segment 4
// End of segments
Assert.Equal(0, merged.IndexOf(CreateTagHelper("TagHelper0"))); // End of segment 1
Assert.Equal(2, merged.IndexOf(CreateTagHelper("TagHelper2"))); // End of segment 2
Assert.Equal(5, merged.IndexOf(CreateTagHelper("TagHelper5"))); // End of segment 3
Assert.Equal(6, merged.IndexOf(CreateTagHelper("TagHelper6"))); // End of segment 4
}
[Fact]
public void MergedCollection_CopyTo_HandlesSegmentBoundaries()
{
// Arrange
var helpers1 = CreateTestTagHelpers(2);
var helpers2 = CreateTestTagHelpers(3, startIndex: 2);
var helpers3 = CreateTestTagHelpers(1, startIndex: 5);
var collection1 = TagHelperCollection.Create(helpers1);
var collection2 = TagHelperCollection.Create(helpers2);
var collection3 = TagHelperCollection.Create(helpers3);
var merged = TagHelperCollection.Merge(ImmutableArray.Create(collection1, collection2, collection3));
var destination = new TagHelperDescriptor[10];
// Act
merged.CopyTo(destination);
// Assert
Assert.Same(helpers1[0], destination[0]);
Assert.Same(helpers1[1], destination[1]);
Assert.Same(helpers2[0], destination[2]);
Assert.Same(helpers2[1], destination[3]);
Assert.Same(helpers2[2], destination[4]);
Assert.Same(helpers3[0], destination[5]);
// Remaining should be null
for (var i = 6; i < 10; i++)
{
Assert.Null(destination[i]);
}
}
[Fact]
public void MergedCollection_ComputeHashCode_IsConsistentWithContent()
{
// Arrange
var helpers = CreateTestTagHelpers(6).AsSpan();
// Create the same content using different merge strategies
var merged1 = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..2]),
TagHelperCollection.Create(helpers[2..6]));
var merged2 = TagHelperCollection.Merge(ImmutableArray.Create(
TagHelperCollection.Create(helpers[0..3]),
TagHelperCollection.Create(helpers[3..6])));
var arrayBacked = TagHelperCollection.Create(helpers);
// Act & Assert
Assert.Equal(arrayBacked.GetHashCode(), merged1.GetHashCode());
Assert.Equal(arrayBacked.GetHashCode(), merged2.GetHashCode());
Assert.Equal(merged1.GetHashCode(), merged2.GetHashCode());
}
[Fact]
public void MergedCollection_EqualsImplementation_WorksAcrossDifferentCollectionTypes()
{
// Arrange
var helpers = CreateTestTagHelpers(4).AsSpan();
var arrayBacked = TagHelperCollection.Create(helpers);
var twoItemMerged = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..2]),
TagHelperCollection.Create(helpers[2..4]));
var multiMerged = TagHelperCollection.Merge(ImmutableArray.Create(
TagHelperCollection.Create([helpers[0]]),
TagHelperCollection.Create([helpers[1]]),
TagHelperCollection.Create(helpers[2..4])));
// Act & Assert - All should be equal despite different internal structures
Assert.True(arrayBacked.Equals(twoItemMerged));
Assert.True(twoItemMerged.Equals(arrayBacked));
Assert.True(arrayBacked.Equals(multiMerged));
Assert.True(multiMerged.Equals(arrayBacked));
Assert.True(twoItemMerged.Equals(multiMerged));
Assert.True(multiMerged.Equals(twoItemMerged));
}
[Fact]
public void MergedCollection_DifferentInternalStructure_SameContent_ReturnsTrue()
{
// Arrange - Create collections with same content but different internal merge structure
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
// First merged collection: [tagHelper1] + [tagHelper2, tagHelper3]
TagHelperCollection firstPart1 = [tagHelper1];
TagHelperCollection secondPart1 = [tagHelper2, tagHelper3];
var merged1 = TagHelperCollection.Merge(firstPart1, secondPart1);
// Second merged collection: [tagHelper1, tagHelper2] + [tagHelper3]
TagHelperCollection firstPart2 = [tagHelper1, tagHelper2];
TagHelperCollection secondPart2 = [tagHelper3];
var merged2 = TagHelperCollection.Merge(firstPart2, secondPart2);
// Act & Assert - Should be equal despite different merge structure
Assert.True(merged1.Equals(merged2));
Assert.True(merged2.Equals(merged1));
Assert.Equal(merged1.GetHashCode(), merged2.GetHashCode());
// Verify content is identical
Assert.Equal(3, merged1.Count);
Assert.Equal(3, merged2.Count);
Assert.Same(tagHelper1, merged1[0]);
Assert.Same(tagHelper2, merged1[1]);
Assert.Same(tagHelper3, merged1[2]);
Assert.Same(tagHelper1, merged2[0]);
Assert.Same(tagHelper2, merged2[1]);
Assert.Same(tagHelper3, merged2[2]);
}
[Fact]
public void TwoItemMergedCollection_DifferentInternalStructure_SameContent_ReturnsTrue()
{
// Arrange - Create two-item merged collections with same content but different internal structure
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
// First: single + single
TagHelperCollection first1 = [tagHelper1];
TagHelperCollection second1 = [tagHelper2];
var merged1 = TagHelperCollection.Merge(first1, second1);
// Second: array + single (different internal collection types)
var arrayCollection = TagHelperCollection.Create([tagHelper1]);
TagHelperCollection singleCollection = [tagHelper2];
var merged2 = TagHelperCollection.Merge(arrayCollection, singleCollection);
// Act & Assert - Should be equal despite different internal structure
Assert.True(merged1.Equals(merged2));
Assert.True(merged2.Equals(merged1));
Assert.Equal(merged1.GetHashCode(), merged2.GetHashCode());
// Verify content is identical
Assert.Equal(2, merged1.Count);
Assert.Equal(2, merged2.Count);
Assert.Same(tagHelper1, merged1[0]);
Assert.Same(tagHelper2, merged1[1]);
Assert.Same(tagHelper1, merged2[0]);
Assert.Same(tagHelper2, merged2[1]);
}
[Fact]
public void MergedCollection_VsArrayCollection_SameContent_ReturnsTrue()
{
// Arrange - Create merged collection vs array-backed collection with same content
var tagHelpers = CreateTestTagHelpers(4).AsSpan();
// Create via merge
var part1 = TagHelperCollection.Create(tagHelpers[0..2]);
var part2 = TagHelperCollection.Create(tagHelpers[2..4]);
var merged = TagHelperCollection.Merge(part1, part2);
// Create directly from array
var arrayBacked = TagHelperCollection.Create(tagHelpers);
// Act & Assert - Should be equal despite different construction
Assert.True(merged.Equals(arrayBacked));
Assert.True(arrayBacked.Equals(merged));
Assert.Equal(merged.GetHashCode(), arrayBacked.GetHashCode());
// Verify content is identical
Assert.Equal(4, merged.Count);
Assert.Equal(4, arrayBacked.Count);
for (var i = 0; i < 4; i++)
{
Assert.Same(tagHelpers[i], merged[i]);
Assert.Same(tagHelpers[i], arrayBacked[i]);
}
}
[Fact]
public void MergedCollection_VsSingleItemCollection_SameContent_ReturnsTrue()
{
// Arrange
var tagHelper = CreateTagHelper("Test");
// Create via merge of two empty parts (should result in single item)
TagHelperCollection part1 = [tagHelper];
var merged = TagHelperCollection.Merge(part1, TagHelperCollection.Empty);
// Create directly as single item
TagHelperCollection single = [tagHelper];
// Act & Assert - Should be equal despite different construction
Assert.True(merged.Equals(single));
Assert.True(single.Equals(merged));
Assert.Equal(merged.GetHashCode(), single.GetHashCode());
// Verify content is identical
Assert.Single(merged);
Assert.Single(single);
Assert.Same(tagHelper, merged[0]);
Assert.Same(tagHelper, single[0]);
}
[Fact]
public void MultiLevelMergedCollection_EqualityWorks_AcrossDifferentNestingLevels()
{
// Arrange - Create the same content through different nesting levels
var helpers = CreateTestTagHelpers(8);
// Flat merge: all 8 collections merged at once
var individualCollections = helpers.Select(h => TagHelperCollection.Create([h])).ToArray();
var flatMerged = TagHelperCollection.Merge(individualCollections.ToImmutableArray());
// Two-level merge: merge pairs, then merge the pairs
var pair1 = TagHelperCollection.Merge(individualCollections[0], individualCollections[1]);
var pair2 = TagHelperCollection.Merge(individualCollections[2], individualCollections[3]);
var pair3 = TagHelperCollection.Merge(individualCollections[4], individualCollections[5]);
var pair4 = TagHelperCollection.Merge(individualCollections[6], individualCollections[7]);
var twoLevelMerged = TagHelperCollection.Merge(ImmutableArray.Create(pair1, pair2, pair3, pair4));
// Three-level merge: merge pairs, then pairs of pairs
var quad1 = TagHelperCollection.Merge(pair1, pair2);
var quad2 = TagHelperCollection.Merge(pair3, pair4);
var threeLevelMerged = TagHelperCollection.Merge(quad1, quad2);
// Array-backed for comparison
var arrayBacked = TagHelperCollection.Create(helpers);
// Act & Assert - All should be equal despite different nesting structures
Assert.True(flatMerged.Equals(twoLevelMerged));
Assert.True(twoLevelMerged.Equals(threeLevelMerged));
Assert.True(threeLevelMerged.Equals(arrayBacked));
Assert.True(arrayBacked.Equals(flatMerged));
// Verify hash codes are equal
Assert.Equal(flatMerged.GetHashCode(), twoLevelMerged.GetHashCode());
Assert.Equal(twoLevelMerged.GetHashCode(), threeLevelMerged.GetHashCode());
Assert.Equal(threeLevelMerged.GetHashCode(), arrayBacked.GetHashCode());
}
[Fact]
public void MergedCollection_WithNestedMergedCollections_EqualityWorksCorrectly()
{
// Arrange - Create complex nested structure
var helpers = CreateTestTagHelpers(6);
// Create nested merged collection structure
var innerMerged1 = TagHelperCollection.Merge(
TagHelperCollection.Create([helpers[0]]),
TagHelperCollection.Create([helpers[1]]));
var innerMerged2 = TagHelperCollection.Merge(
TagHelperCollection.Create([helpers[2], helpers[3]]),
TagHelperCollection.Create([helpers[4], helpers[5]]));
var outerMerged = TagHelperCollection.Merge(innerMerged1, innerMerged2);
// Create equivalent flat structure
var flatMerged = TagHelperCollection.Merge(ImmutableArray.Create(
TagHelperCollection.Create([helpers[0]]),
TagHelperCollection.Create([helpers[1]]),
TagHelperCollection.Create([helpers[2], helpers[3]]),
TagHelperCollection.Create([helpers[4], helpers[5]])
));
// Create array-backed equivalent
var arrayBacked = TagHelperCollection.Create(helpers);
// Act & Assert
Assert.True(outerMerged.Equals(flatMerged));
Assert.True(flatMerged.Equals(arrayBacked));
Assert.True(arrayBacked.Equals(outerMerged));
// Verify content integrity
Assert.Equal(6, outerMerged.Count);
Assert.Equal(6, flatMerged.Count);
Assert.Equal(6, arrayBacked.Count);
for (var i = 0; i < 6; i++)
{
Assert.Same(helpers[i], outerMerged[i]);
Assert.Same(helpers[i], flatMerged[i]);
Assert.Same(helpers[i], arrayBacked[i]);
}
}
[Fact]
public void MergedCollection_DifferentContent_SameStructure_ReturnsFalse()
{
// Arrange - Same merge structure but different content
var helpers1 = CreateTestTagHelpers(4).AsSpan();
var helpers2 = CreateTestTagHelpers(4, startIndex: 10).AsSpan(); // Different content
var part1a = TagHelperCollection.Create(helpers1[0..2]);
var part2a = TagHelperCollection.Create(helpers1[2..4]);
var merged1 = TagHelperCollection.Merge(part1a, part2a);
var part1b = TagHelperCollection.Create(helpers2[0..2]);
var part2b = TagHelperCollection.Create(helpers2[2..4]);
var merged2 = TagHelperCollection.Merge(part1b, part2b);
// Act & Assert - Should not be equal due to different content
Assert.False(merged1.Equals(merged2));
Assert.False(merged2.Equals(merged1));
Assert.NotEqual(merged1.GetHashCode(), merged2.GetHashCode());
}
[Fact]
public void MergedCollection_SameContentDifferentOrder_ReturnsFalse()
{
// Arrange - Same items but different order
var tagHelper1 = CreateTagHelper("Test1");
var tagHelper2 = CreateTagHelper("Test2");
var tagHelper3 = CreateTagHelper("Test3");
// First order: 1, 2, 3
var merged1 = TagHelperCollection.Merge(
TagHelperCollection.Create([tagHelper1]),
TagHelperCollection.Create([tagHelper2, tagHelper3]));
// Second order: 3, 2, 1
var merged2 = TagHelperCollection.Merge(
TagHelperCollection.Create([tagHelper3]),
TagHelperCollection.Create([tagHelper2, tagHelper1]));
// Act & Assert - Should not be equal due to different order
Assert.False(merged1.Equals(merged2));
Assert.False(merged2.Equals(merged1));
Assert.NotEqual(merged1.GetHashCode(), merged2.GetHashCode());
// Verify the order is actually different
Assert.Same(tagHelper1, merged1[0]);
Assert.Same(tagHelper2, merged1[1]);
Assert.Same(tagHelper3, merged1[2]);
Assert.Same(tagHelper3, merged2[0]);
Assert.Same(tagHelper2, merged2[1]);
Assert.Same(tagHelper1, merged2[2]);
}
[Fact]
public void MergedCollection_ChecksumBasedEquality_WorksWithLargeCollections()
{
// Arrange - Create large collections to test checksum-based equality path
var helpers = CreateTestTagHelpers(100).AsSpan();
// Create via different merge strategies
var merged1 = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..50]),
TagHelperCollection.Create(helpers[50..100]));
var merged2 = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..25]),
TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[25..75]),
TagHelperCollection.Create(helpers[75..100])));
var arrayBacked = TagHelperCollection.Create(helpers);
// Act & Assert - Large collections should use checksum-based equality
Assert.True(merged1.Equals(merged2));
Assert.True(merged2.Equals(arrayBacked));
Assert.True(arrayBacked.Equals(merged1));
// Hash codes should be equal due to checksum-based computation
Assert.Equal(merged1.GetHashCode(), merged2.GetHashCode());
Assert.Equal(merged2.GetHashCode(), arrayBacked.GetHashCode());
}
[Fact]
public void MergedCollection_TransitiveEquality_WorksCorrectly()
{
// Arrange - Test transitive property: if A == B and B == C, then A == C
var helpers = CreateTestTagHelpers(6).AsSpan();
var collectionA = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..2]),
TagHelperCollection.Create(helpers[2..6]));
var collectionB = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..3]),
TagHelperCollection.Create(helpers[3..6]));
var collectionC = TagHelperCollection.Create(helpers);
// Act & Assert - Test transitivity
Assert.True(collectionA.Equals(collectionB)); // A == B
Assert.True(collectionB.Equals(collectionC)); // B == C
Assert.True(collectionA.Equals(collectionC)); // Therefore A == C
// Test symmetry
Assert.True(collectionB.Equals(collectionA)); // B == A
Assert.True(collectionC.Equals(collectionB)); // C == B
Assert.True(collectionC.Equals(collectionA)); // C == A
// Test reflexivity
Assert.True(collectionA.Equals(collectionA));
Assert.True(collectionB.Equals(collectionB));
Assert.True(collectionC.Equals(collectionC));
}
[Fact]
public void MergedCollection_NullEquality_WorksCorrectly()
{
// Arrange
var helpers = CreateTestTagHelpers(3).AsSpan();
var merged = TagHelperCollection.Merge(
TagHelperCollection.Create([helpers[0]]),
TagHelperCollection.Create(helpers[1..3]));
// Act & Assert
Assert.False(merged.Equals(null));
Assert.False(merged.Equals((object?)null));
}
[Fact]
public void MergedCollection_EqualityWithDifferentTypes_WorksCorrectly()
{
// Arrange
var helpers = CreateTestTagHelpers(3).AsSpan();
var merged = TagHelperCollection.Merge(
TagHelperCollection.Create([helpers[0]]),
TagHelperCollection.Create(helpers[1..3]));
// Act & Assert - Should not equal different types
Assert.False(merged.Equals("not a collection"));
Assert.False(merged.Equals(42));
}
[Fact]
public void MergedCollection_EqualsObjectOverride_WorksCorrectly()
{
// Arrange
var helpers = CreateTestTagHelpers(3).AsSpan();
var merged1 = TagHelperCollection.Merge(
TagHelperCollection.Create([helpers[0]]),
TagHelperCollection.Create(helpers[1..3]));
var merged2 = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..2]),
TagHelperCollection.Create([helpers[2]]));
// Act & Assert - Test object.Equals override
Assert.True(merged1.Equals((object)merged2));
Assert.True(merged2.Equals((object)merged1));
Assert.True(merged1.Equals((object)merged1)); // Reflexivity
}
[Fact]
public void MergedCollection_EnumerationResetAndDispose_WorksCorrectly()
{
// Arrange
var helpers = CreateTestTagHelpers(6).AsSpan();
var merged = TagHelperCollection.Merge(ImmutableArray.Create(
TagHelperCollection.Create(helpers[0..2]),
TagHelperCollection.Create(helpers[2..4]),
TagHelperCollection.Create(helpers[4..6])));
// Act & Assert - Test enumerator lifecycle
using var enumerator = merged.GetEnumerator();
// Enumerate first few items
Assert.True(enumerator.MoveNext());
Assert.Same(helpers[0], enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Same(helpers[1], enumerator.Current);
// Reset should work
enumerator.Reset();
Assert.True(enumerator.MoveNext());
Assert.Same(helpers[0], enumerator.Current);
// Dispose should work (multiple times)
enumerator.Dispose();
enumerator.Dispose(); // Should not throw
}
[Fact]
public void MergedCollection_IndexOfWithNestedMergedCollections_WorksCorrectly()
{
// Arrange - Create nested merged collections to test complex scenarios
var helpers = CreateTestTagHelpers(12).AsSpan();
var innerMerged1 = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[0..2]),
TagHelperCollection.Create(helpers[2..4]));
var innerMerged2 = TagHelperCollection.Merge(
TagHelperCollection.Create(helpers[4..6]),
TagHelperCollection.Create(helpers[6..8]));
var regularCollection = TagHelperCollection.Create(helpers[8..12]);
var outerMerged = TagHelperCollection.Merge(ImmutableArray.Create(
innerMerged1, innerMerged2, regularCollection));
// Act & Assert - Verify IndexOf works correctly for nested structure
for (var i = 0; i < 12; i++)
{
Assert.Equal(i, outerMerged.IndexOf(helpers[i]));
Assert.Same(helpers[i], outerMerged[i]);
}
// Test non-existent item
var nonExistent = CreateTagHelper("NonExistent");
Assert.Equal(-1, outerMerged.IndexOf(nonExistent));
}
[Fact]
public void MergedCollection_LargeNumberOfSmallSegments_PerformsWell()
{
// Arrange - Stress test with many small segments
var segments = new List<TagHelperCollection>();
var allHelpers = new List<TagHelperDescriptor>();
for (var i = 0; i < 50; i++)
{
var helper = CreateTagHelper($"Helper{i}");
segments.Add(TagHelperCollection.Create([helper]));
allHelpers.Add(helper);
}
var merged = TagHelperCollection.Merge(segments.ToImmutableArray());
// Act & Assert - Verify functionality with many segments
Assert.Equal(50, merged.Count);
// Test enumeration
var enumerated = new List<TagHelperDescriptor>();
foreach (var item in merged)
{
enumerated.Add(item);
}
Assert.Equal(allHelpers, enumerated);
// Test random access
for (var i = 0; i < 50; i += 5) // Test every 5th element
{
Assert.Same(allHelpers[i], merged[i]);
Assert.Equal(i, merged.IndexOf(allHelpers[i]));
}
}
[Fact]
public void Where_EmptyCollection_ReturnsEmpty()
{
// Arrange
var collection = TagHelperCollection.Empty;
// Act
var filtered = collection.Where(h => h.Name.StartsWith("Test", StringComparison.Ordinal));
// Assert
Assert.Same(TagHelperCollection.Empty, filtered);
}
[Fact]
public void Where_NoMatches_ReturnsEmpty()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
// Act
var filtered = collection.Where(h => h.Name.StartsWith("NonExistent", StringComparison.Ordinal));
// Assert
Assert.Same(TagHelperCollection.Empty, filtered);
}
[Fact]
public void Where_AllMatch_ReturnsSameCollection()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(3);
var collection = TagHelperCollection.Create(tagHelpers);
// Act
var filtered = collection.Where(h => h.Name.StartsWith("TagHelper", StringComparison.Ordinal));
// Assert
Assert.Equal(collection.Count, filtered.Count);
Assert.SameItems(tagHelpers, filtered);
// Verify the result has the same structure (should be optimized)
for (var i = 0; i < tagHelpers.Length; i++)
{
Assert.Same(tagHelpers[i], filtered[i]);
}
}
[Fact]
public void Where_PartialMatch_ReturnsFilteredCollection()
{
// Arrange
var tagHelper1 = CreateTagHelper("TestHelper1");
var tagHelper2 = CreateTagHelper("OtherHelper");
var tagHelper3 = CreateTagHelper("TestHelper3");
var tagHelpers = new[] { tagHelper1, tagHelper2, tagHelper3 };
var collection = TagHelperCollection.Create(tagHelpers);
// Act
var filtered = collection.Where(h => h.Name.StartsWith("Test", StringComparison.Ordinal));
// Assert
Assert.Equal(2, filtered.Count);
Assert.Same(tagHelper1, filtered[0]);
Assert.Same(tagHelper3, filtered[1]);
Assert.Equal(0, filtered.IndexOf(tagHelper1));
Assert.Equal(1, filtered.IndexOf(tagHelper3));
Assert.True(filtered.Contains(tagHelper1));
Assert.True(filtered.Contains(tagHelper3));
Assert.False(filtered.Contains(tagHelper2));
}
[Fact]
public void Where_SingleItem_MatchingPredicate_ReturnsSingleItemCollection()
{
// Arrange
var tagHelper = CreateTagHelper("TestHelper");
TagHelperCollection collection = [tagHelper];
// Act
var filtered = collection.Where(h => h.Name.StartsWith("Test", StringComparison.Ordinal));
// Assert
Assert.Single(filtered);
Assert.Same(tagHelper, filtered[0]);
Assert.Equal(0, filtered.IndexOf(tagHelper));
Assert.True(filtered.Contains(tagHelper));
}
[Fact]
public void Where_SingleItem_NonMatchingPredicate_ReturnsEmpty()
{
// Arrange
var tagHelper = CreateTagHelper("TestHelper");
TagHelperCollection collection = [tagHelper];
// Act
var filtered = collection.Where(h => h.Name.StartsWith("Other", StringComparison.Ordinal));
// Assert
Assert.Same(TagHelperCollection.Empty, filtered);
}
[Fact]
public void Where_MergedCollection_WorksCorrectly()
{
// Arrange
var firstHelpers = CreateTestTagHelpers(2);
var secondHelpers = new[] { CreateTagHelper("OtherHelper1"), CreateTagHelper("TestHelper") };
var first = TagHelperCollection.Create(firstHelpers);
var second = TagHelperCollection.Create(secondHelpers);
var merged = TagHelperCollection.Merge(first, second);
// Act
var filtered = merged.Where(h => h.Name.StartsWith("TagHelper", StringComparison.Ordinal));
// Assert
Assert.Equal(2, filtered.Count); // 2 from first, 0 from second (neither "OtherHelper1" nor "TestHelper" starts with "TagHelper")
Assert.Same(firstHelpers[0], filtered[0]);
Assert.Same(firstHelpers[1], filtered[1]);
}
[Fact]
public void Where_LargeCollection_WorksCorrectly()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(100);
var collection = TagHelperCollection.Create(tagHelpers);
// Act - Filter for even-numbered helpers
var filtered = collection.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
return int.Parse(nameIndex) % 2 == 0;
});
// Assert
Assert.Equal(50, filtered.Count); // TagHelper0, TagHelper2, ..., TagHelper98
// Verify the filtered items are correct
for (var i = 0; i < 50; i++)
{
var expectedIndex = i * 2;
Assert.Same(tagHelpers[expectedIndex], filtered[i]);
Assert.Equal(i, filtered.IndexOf(tagHelpers[expectedIndex]));
}
}
[Fact]
public void Where_ComplexPredicate_WorksCorrectly()
{
// Arrange
var shortHelper = CreateTagHelper("A");
var mediumHelper1 = CreateTagHelper("Medium1");
var mediumHelper2 = CreateTagHelper("Medium2");
var longHelper = CreateTagHelper("VeryLongHelperName");
var collection = TagHelperCollection.Create([shortHelper, mediumHelper1, mediumHelper2, longHelper]);
// Act
var filtered = collection.Where(h => h.Name.Length >= 6 && h.Name.Length <= 8);
// Assert
Assert.Equal(2, filtered.Count);
Assert.Same(mediumHelper1, filtered[0]);
Assert.Same(mediumHelper2, filtered[1]);
}
[Fact]
public void Where_ChainedFiltering_WorksCorrectly()
{
// Arrange
var helpers = new[]
{
CreateTagHelper("TestHelper1"),
CreateTagHelper("TestHelper2"),
CreateTagHelper("OtherHelper1"),
CreateTagHelper("TestHelper3"),
CreateTagHelper("OtherHelper2")
};
var collection = TagHelperCollection.Create(helpers);
// Act - Chain multiple Where operations
var filtered = collection
.Where(h => h.Name.StartsWith("Test", StringComparison.Ordinal))
.Where(h => !h.Name.EndsWith('2'));
// Assert
Assert.Equal(2, filtered.Count);
Assert.Same(helpers[0], filtered[0]); // TestHelper1
Assert.Same(helpers[3], filtered[1]); // TestHelper3
}
[Fact]
public void Where_PreservesSegmentStructure_OptimalCase()
{
// Arrange - Create a multi-segment collection where filtering preserves segment boundaries
var segment1Helpers = new[] { CreateTagHelper("Keep1"), CreateTagHelper("Keep2") };
var segment2Helpers = new[] { CreateTagHelper("Keep3"), CreateTagHelper("Keep4") };
var collection1 = TagHelperCollection.Create(segment1Helpers);
var collection2 = TagHelperCollection.Create(segment2Helpers);
var merged = TagHelperCollection.Merge(collection1, collection2);
// Act
var filtered = merged.Where(h => h.Name.StartsWith("Keep", StringComparison.Ordinal));
// Assert
Assert.Equal(4, filtered.Count);
Assert.Same(segment1Helpers[0], filtered[0]);
Assert.Same(segment1Helpers[1], filtered[1]);
Assert.Same(segment2Helpers[0], filtered[2]);
Assert.Same(segment2Helpers[1], filtered[3]);
// Verify filtering performance by checking all items are accessible
for (var i = 0; i < 4; i++)
{
var helper = filtered[i];
Assert.Equal(i, filtered.IndexOf(helper));
Assert.True(filtered.Contains(helper));
}
}
[Fact]
public void Where_FragmentedSegments_CreatesOptimalSegments()
{
// Arrange - Create a scenario where filtering creates fragmented segments
var helpers = new[]
{
CreateTagHelper("Keep1"), // Keep
CreateTagHelper("Remove1"), // Remove
CreateTagHelper("Keep2"), // Keep
CreateTagHelper("Keep3"), // Keep
CreateTagHelper("Remove2"), // Remove
CreateTagHelper("Keep4") // Keep
};
var collection = TagHelperCollection.Create(helpers);
// Act
var filtered = collection.Where(h => h.Name.StartsWith("Keep", StringComparison.Ordinal));
// Assert
Assert.Equal(4, filtered.Count);
Assert.Same(helpers[0], filtered[0]); // Keep1
Assert.Same(helpers[2], filtered[1]); // Keep2
Assert.Same(helpers[3], filtered[2]); // Keep3
Assert.Same(helpers[5], filtered[3]); // Keep4
// Verify all operations work correctly on fragmented result
Assert.Equal(0, filtered.IndexOf(helpers[0]));
Assert.Equal(1, filtered.IndexOf(helpers[2]));
Assert.Equal(2, filtered.IndexOf(helpers[3]));
Assert.Equal(3, filtered.IndexOf(helpers[5]));
Assert.Equal(-1, filtered.IndexOf(helpers[1])); // Removed item
Assert.Equal(-1, filtered.IndexOf(helpers[4])); // Removed item
}
[Fact]
public void Where_AlternatingPattern_CreatesMultipleSegments()
{
// Arrange - Create an alternating keep/remove pattern
var helpers = new TagHelperDescriptor[10];
for (var i = 0; i < 10; i++)
{
helpers[i] = CreateTagHelper(i % 2 == 0 ? $"Keep{i}" : $"Remove{i}");
}
var collection = TagHelperCollection.Create(helpers);
// Act
var filtered = collection.Where(h => h.Name.StartsWith("Keep", StringComparison.Ordinal));
// Assert
Assert.Equal(5, filtered.Count);
for (var i = 0; i < 5; i++)
{
var expectedIndex = i * 2;
Assert.Same(helpers[expectedIndex], filtered[i]);
}
}
[Fact]
public void Where_FilteredResult_SupportsAllOperations()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(6);
var collection = TagHelperCollection.Create(tagHelpers);
// Act
var filtered = collection.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
var index = int.Parse(nameIndex);
return index % 2 == 0; // Keep even indices: 0, 2, 4
});
// Assert
Assert.Equal(3, filtered.Count);
// Test indexer
Assert.Same(tagHelpers[0], filtered[0]);
Assert.Same(tagHelpers[2], filtered[1]);
Assert.Same(tagHelpers[4], filtered[2]);
// Test IndexOf
Assert.Equal(0, filtered.IndexOf(tagHelpers[0]));
Assert.Equal(1, filtered.IndexOf(tagHelpers[2]));
Assert.Equal(2, filtered.IndexOf(tagHelpers[4]));
Assert.Equal(-1, filtered.IndexOf(tagHelpers[1])); // Filtered out
Assert.Equal(-1, filtered.IndexOf(tagHelpers[3])); // Filtered out
Assert.Equal(-1, filtered.IndexOf(tagHelpers[5])); // Filtered out
// Test Contains
Assert.True(filtered.Contains(tagHelpers[0]));
Assert.True(filtered.Contains(tagHelpers[2]));
Assert.True(filtered.Contains(tagHelpers[4]));
Assert.False(filtered.Contains(tagHelpers[1]));
Assert.False(filtered.Contains(tagHelpers[3]));
Assert.False(filtered.Contains(tagHelpers[5]));
// Test CopyTo
var destination = new TagHelperDescriptor[5];
filtered.CopyTo(destination);
Assert.Same(tagHelpers[0], destination[0]);
Assert.Same(tagHelpers[2], destination[1]);
Assert.Same(tagHelpers[4], destination[2]);
Assert.Null(destination[3]);
Assert.Null(destination[4]);
// Test enumeration
var enumerated = new List<TagHelperDescriptor>();
foreach (var item in filtered)
{
enumerated.Add(item);
}
Assert.Equal(3, enumerated.Count);
Assert.Same(tagHelpers[0], enumerated[0]);
Assert.Same(tagHelpers[2], enumerated[1]);
Assert.Same(tagHelpers[4], enumerated[2]);
}
[Fact]
public void Where_FilteredResult_SupportsEquality()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(4);
var collection1 = TagHelperCollection.Create(tagHelpers);
var collection2 = TagHelperCollection.Create(tagHelpers);
// Act
var filtered1 = collection1.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
return int.Parse(nameIndex) < 2; // Keep 0, 1
});
var filtered2 = collection2.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
return int.Parse(nameIndex) < 2; // Keep 0, 1
});
// Assert
Assert.True(filtered1.Equals(filtered2));
Assert.True(filtered2.Equals(filtered1));
Assert.Equal(filtered1.GetHashCode(), filtered2.GetHashCode());
}
[Fact]
public void Where_FilteredResult_DifferentContent_NotEqual()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(4);
var collection = TagHelperCollection.Create(tagHelpers);
// Act
var filtered1 = collection.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
return int.Parse(nameIndex) < 2; // Keep 0, 1
});
var filtered2 = collection.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
return int.Parse(nameIndex) >= 2; // Keep 2, 3
});
// Assert
Assert.False(filtered1.Equals(filtered2));
Assert.False(filtered2.Equals(filtered1));
Assert.NotEqual(filtered1.GetHashCode(), filtered2.GetHashCode());
}
[Fact]
public void Where_FilteredResult_ThrowsOnInvalidIndex()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(4);
var collection = TagHelperCollection.Create(tagHelpers);
var filtered = collection.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
return int.Parse(nameIndex) < 2; // Keep 0, 1
});
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => filtered[-1]);
Assert.Throws<ArgumentOutOfRangeException>(() => filtered[2]);
Assert.Throws<ArgumentOutOfRangeException>(() => filtered[10]);
}
[Fact]
public void Where_FilteredResult_CopyToDestinationTooShort_ThrowsArgumentException()
{
// Arrange
var tagHelpers = CreateTestTagHelpers(4);
var collection = TagHelperCollection.Create(tagHelpers);
var filtered = collection.Where(h =>
{
var nameIndex = h.Name["TagHelper".Length..];
return int.Parse(nameIndex) < 3; // Keep 0, 1, 2
});
var destination = new TagHelperDescriptor[2]; // Too short
// Act & Assert
Assert.Throws<ArgumentException>(() => filtered.CopyTo(destination));
}
}
|