diff --git a/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs b/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs index 6e21d11..b297bc2 100644 --- a/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs +++ b/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs @@ -80,6 +80,40 @@ public SmallDictionary(IComparer comparer) _comparer = comparer; } + /// + /// Initializes a new instance of the class that contains + /// entries copied from the specified collection and uses the specified key comparer. + /// + /// The collection whose entries are copied to the new dictionary. + /// The comparer to use when comparing keys. + /// + /// or is . + /// + public SmallDictionary(IEnumerable> collection, IComparer comparer) : this(comparer) + { + ArgumentNullException.ThrowIfNull(collection); + + if (collection is Dictionary dictionary) + { + _items = new KeyValuePair[dictionary.Count]; + foreach (var pair in dictionary) + Add(pair.Key, pair.Value); + } + else if (collection is SmallDictionary small) + { + _count = small._count; + _items = [..small._items]; + + if (_count > LinearSearchThreshold && !ReferenceEquals(_comparer, small._comparer)) + Array.Sort(_items, 0, _count, new KeyValuePairComparer(_comparer)); + } + else + { + foreach (var pair in collection) + Add(pair.Key, pair.Value); + } + } + /// public bool ContainsKey(TKey key) => IndexOf(key) >= 0; @@ -111,6 +145,26 @@ public void Add(TKey key, TValue value) Insert(~index, key, value); } + /// + /// Attempts to add the specified key and value to the dictionary. + /// + /// The key of the entry to add. + /// The value of the entry to add. + /// + /// if the key/value pair was added to the dictionary; + /// otherwise, . + /// + /// is . + public bool TryAdd(TKey key, TValue value) + { + var index = IndexOf(key); + if (index >= 0) + return false; + + Insert(~index, key, value); + return true; + } + /// public bool Remove(TKey key) { diff --git a/tests/Ramstack.HtmxToolkit.Tests/Collections/SmallDictionaryTests.cs b/tests/Ramstack.HtmxToolkit.Tests/Collections/SmallDictionaryTests.cs index 3fe0de2..ff25752 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/Collections/SmallDictionaryTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/Collections/SmallDictionaryTests.cs @@ -21,6 +21,95 @@ public void Constructor_NewInstance_IsEmpty_Writable() }); } + [Test] + public void Constructor_FromCollection_CopiesEntries() + { + var source = new[] + { + KeyValuePair.Create(1, "one"), + KeyValuePair.Create(2, "two"), + KeyValuePair.Create(3, "three") + }; + + var dictionary = new SmallDictionary(source, Comparer.Default); + + Assert.Multiple(() => + { + Assert.That(dictionary.Count, Is.EqualTo(3)); + Assert.That(dictionary, Is.EquivalentTo(source)); + Assert.That(dictionary[2], Is.EqualTo("two")); + }); + } + + [Test] + public void Constructor_FromDictionary_CopiesEntries() + { + var source = new Dictionary + { + [1] = "one", + [2] = "two", + [3] = "three" + }; + + var dictionary = new SmallDictionary(source, Comparer.Default); + + Assert.Multiple(() => + { + Assert.That(dictionary.Count, Is.EqualTo(3)); + Assert.That(dictionary, Is.EquivalentTo(source)); + }); + } + + [Test] + public void Constructor_FromSmallDictionary_CopiesEntries() + { + var source = CreateDictionary(); + source.Add(3, "three"); + source.Add(1, "one"); + source.Add(2, "two"); + + var dictionary = new SmallDictionary(source, Comparer.Default); + + Assert.Multiple(() => + { + Assert.That(dictionary.Count, Is.EqualTo(3)); + Assert.That(dictionary, Is.EquivalentTo(source)); + }); + } + + [Test] + public void Constructor_FromSmallDictionary_WithDifferentComparer_ReSortsEntries() + { + var source = new SmallDictionary(StringComparer.Ordinal); + foreach (var key in new[] { "Bravo", "alpha", "Delta", "charlie", "Echo", "foxtrot" }) + source.Add(key, source.Count + 1); + + var dictionary = new SmallDictionary(source, StringComparer.OrdinalIgnoreCase); + + Assert.Multiple(() => + { + Assert.That(dictionary.Count, Is.EqualTo(source.Count)); + Assert.That(dictionary, Is.EquivalentTo(source)); + Assert.That(dictionary["ALPHA"], Is.EqualTo(source["alpha"])); + Assert.That(dictionary["echo"], Is.EqualTo(source["Echo"])); + Assert.That(dictionary["FOXTROT"], Is.EqualTo(source["foxtrot"])); + }); + } + + [Test] + public void Constructor_NullCollection_ThrowsArgumentNullException() + { + Assert.Throws( + () => new SmallDictionary(null!, Comparer.Default)); + } + + [Test] + public void Constructor_NullComparer_ThrowsArgumentNullException() + { + Assert.Throws( + () => new SmallDictionary([], null!)); + } + [Test] public void Add_NewKey_StoresEntry_UpdatesAllViews() { @@ -67,6 +156,52 @@ public void Add_NullKey_ThrowsArgumentNullException_PreservesState() }); } + [Test] + public void TryAdd_NewKey_ReturnsTrue_AddsEntry() + { + var dictionary = new SmallDictionary(Comparer.Default); + + var added = dictionary.TryAdd(7, "seven"); + + Assert.Multiple(() => + { + Assert.That(added, Is.True); + Assert.That(dictionary.Count, Is.EqualTo(1)); + Assert.That(dictionary[7], Is.EqualTo("seven")); + }); + } + + [Test] + public void TryAdd_ExistingKey_ReturnsFalse_PreservesExistingEntry() + { + var dictionary = new SmallDictionary(Comparer.Default); + dictionary.Add(7, "original"); + + var added = dictionary.TryAdd(7, "replacement"); + + Assert.Multiple(() => + { + Assert.That(added, Is.False); + Assert.That(dictionary.Count, Is.EqualTo(1)); + Assert.That(dictionary[7], Is.EqualTo("original")); + }); + } + + [Test] + public void TryAdd_NullKey_ThrowsArgumentNullException_PreservesState() + { + var dictionary = new SmallDictionary(StringComparer.Ordinal); + dictionary.Add("existing", 1); + + Assert.Throws(() => dictionary.TryAdd(null!, 0)); + + Assert.Multiple(() => + { + Assert.That(dictionary.Count, Is.EqualTo(1)); + Assert.That(dictionary["existing"], Is.EqualTo(1)); + }); + } + [Test] public void Indexer_Get_MissingKey_ThrowsKeyNotFoundException() {