diff --git a/.BinaryPrefs/Packages/manifest.json b/.BinaryPrefs/Packages/manifest.json index d53a375..07cd7a3 100644 --- a/.BinaryPrefs/Packages/manifest.json +++ b/.BinaryPrefs/Packages/manifest.json @@ -2,13 +2,11 @@ "dependencies": { "com.appegy.binary-prefs": "file:../..", "com.boundfoxstudios.fluentassertions": "6.8.0", - "com.dbrizov.naughtyattributes": "https://github.com/dbrizov/NaughtyAttributes.git#upm", "com.unity.2d.sprite": "1.0.0", - "com.unity.ide.rider": "3.0.28", - "com.unity.ide.visualstudio": "2.0.22", + "com.unity.ide.rider": "3.0.31", "com.unity.mobile.android-logcat": "1.4.2", "com.unity.test-framework": "2.0.1-pre.18", - "com.unity.textmeshpro": "3.0.6", + "com.unity.textmeshpro": "3.0.9", "com.unity.ugui": "1.0.0", "com.yasirkula.ingamedebugconsole": "https://github.com/yasirkula/UnityIngameDebugConsole.git", "net.tnrd.nsubstitute": "5.1.0", diff --git a/.BinaryPrefs/Packages/packages-lock.json b/.BinaryPrefs/Packages/packages-lock.json index 9f22339..cd68fa1 100644 --- a/.BinaryPrefs/Packages/packages-lock.json +++ b/.BinaryPrefs/Packages/packages-lock.json @@ -13,13 +13,6 @@ "dependencies": {}, "url": "https://package.openupm.com" }, - "com.dbrizov.naughtyattributes": { - "version": "https://github.com/dbrizov/NaughtyAttributes.git#upm", - "depth": 0, - "source": "git", - "dependencies": {}, - "hash": "8a8fa5a9659a6d63f196391c71e06c4286c8acd7" - }, "com.unity.2d.sprite": { "version": "1.0.0", "depth": 0, @@ -34,7 +27,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.28", + "version": "3.0.31", "depth": 0, "source": "registry", "dependencies": { @@ -42,15 +35,6 @@ }, "url": "https://packages.unity.com" }, - "com.unity.ide.visualstudio": { - "version": "2.0.22", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.9" - }, - "url": "https://packages.unity.com" - }, "com.unity.mobile.android-logcat": { "version": "1.4.2", "depth": 0, @@ -70,7 +54,7 @@ "url": "https://packages.unity.com" }, "com.unity.textmeshpro": { - "version": "3.0.6", + "version": "3.0.9", "depth": 0, "source": "registry", "dependencies": { diff --git a/Runtime/BinaryStorage.Builder.cs b/Runtime/BinaryStorage.Builder.cs index a361241..4295fa9 100644 --- a/Runtime/BinaryStorage.Builder.cs +++ b/Runtime/BinaryStorage.Builder.cs @@ -11,9 +11,7 @@ public partial class BinaryStorage static partial void ThrowIfFilePathLocked(string filePath); static partial void UnlockFilePathInEditor(string filePath); - /// - /// Creates and configures a new instance of with default settings. - /// + /// Creates and configures a new instance of with default settings. /// The file path for the storage. /// A configured instance. public static BinaryStorage Get(string filePath) @@ -24,9 +22,7 @@ public static BinaryStorage Get(string filePath) .Build(); } - /// - /// Begins the construction of a new instance. - /// + /// Begins the construction of a new instance. /// The file path for the storage. /// A for configuring the instance. public static Builder Construct(string filePath) @@ -36,9 +32,7 @@ public static Builder Construct(string filePath) return new Builder(filePath); } - /// - /// Deletes the storage file at the specified path. - /// + /// Deletes the storage file at the specified path. /// The path to the storage file. internal static void Delete(string storagePath) { @@ -48,9 +42,7 @@ internal static void Delete(string storagePath) } } - /// - /// Provides a fluent interface for configuring and building a instance. - /// + /// Provides a fluent interface for configuring and building a instance. public class Builder { private readonly string _filePath; @@ -64,9 +56,7 @@ internal Builder(string filePath) _filePath = filePath; } - /// - /// Enables automatic saving of changes to the storage. - /// + /// Enables automatic saving of changes to the storage. /// The current instance for method chaining. public Builder EnableAutoSaveOnChange() { @@ -74,18 +64,14 @@ public Builder EnableAutoSaveOnChange() return this; } - /// - /// Specifies the behavior when a requested key is not found in the storage. - /// + /// Specifies the behavior when a requested key is not found in the storage. /// The current instance for method chaining. public Builder SetMissingKeyBehaviour(MissingKeyBehavior behavior) { _missingKeyBehavior = behavior; return this; } - /// - /// Specifies the behavior when the type of value associated with a key does not match the expected type. - /// + /// Specifies the behavior when the type of value associated with a key does not match the expected type. /// The type mismatch behavior. /// The current instance for method chaining. public Builder SetTypeMismatchBehaviour(TypeMismatchBehaviour behavior) @@ -94,9 +80,7 @@ public Builder SetTypeMismatchBehaviour(TypeMismatchBehaviour behavior) return this; } - /// - /// Adds serializers for primitive types to the storage configuration. - /// + /// Adds serializers for primitive types to the storage configuration. /// The current instance for method chaining. public Builder AddPrimitiveTypes() { @@ -124,9 +108,7 @@ public Builder AddPrimitiveTypes() .AddTypeSerializer(Vector3IntSerializer.Shared); } - /// - /// Adds a serializer for a specified type to the storage configuration. - /// + /// Adds a serializer for a specified type to the storage configuration. /// The type to be serialized. /// The serializer for the specified type. /// The current instance for method chaining. @@ -141,9 +123,7 @@ public Builder AddTypeSerializer(TypeSerializer typeSerializer) return this; } - /// - /// Adds support for a specified enum type to the storage configuration. - /// + /// Adds support for a specified enum type to the storage configuration. /// The enum type to be supported. /// Whether to use the full name of the enum type. /// The current instance for method chaining. @@ -185,9 +165,7 @@ public Builder SupportEnum(bool useFullName = false) return this; } - /// - /// Adds support for lists of a specified type to the storage configuration. - /// + /// Adds support for lists of a specified type to the storage configuration. /// The type of elements in the list. /// The current instance for method chaining. /// Thrown if the specified type is not supported. @@ -201,9 +179,7 @@ public Builder SupportListsOf() return AddTypeSerializer(new CollectionTypeSerializer>(section.Serializer)); } - /// - /// Adds support for sets of a specified type to the storage configuration. - /// + /// Adds support for sets of a specified type to the storage configuration. /// The type of elements in the set. /// The current instance for method chaining. /// Thrown if the specified type is not supported. @@ -217,9 +193,7 @@ public Builder SupportSetsOf() return AddTypeSerializer(new CollectionTypeSerializer>(section.Serializer)); } - /// - /// Adds support for dictionaries of specified key and value types to the storage configuration. - /// + /// Adds support for dictionaries of specified key and value types to the storage configuration. /// The type of the dictionary keys. /// The type of the dictionary values. /// The current instance for method chaining. @@ -236,11 +210,10 @@ public Builder SupportDictionariesOf() return AddTypeSerializer(new CollectionTypeSerializer, ReactiveDictionary>(kvSerializer)); } - /// - /// Builds and returns the configured instance. - /// + /// Builds and returns the configured instance. /// The configured instance. - /// Thrown if the storage fails to load data from disk. + /// Thrown if the storage is disposed. + /// An I/O error occurred public BinaryStorage Build() { try diff --git a/Runtime/BinaryStorage.cs b/Runtime/BinaryStorage.cs index 7be8053..6952b45 100644 --- a/Runtime/BinaryStorage.cs +++ b/Runtime/BinaryStorage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using JetBrains.Annotations; using UnityEngine; @@ -7,44 +8,31 @@ namespace Appegy.Storage { - /// - /// Manages a binary storage system for saving, retrieving, and managing records of various types. - /// + /// Manages a binary storage system for saving, retrieving, and managing records of various types. public partial class BinaryStorage : IDisposable { private readonly string _storageFilePath; private readonly IReadOnlyList _supportedTypes; private readonly Dictionary _data = new(); + private readonly Dictionary _collections = new(); private int _changeScopeCounter; - /// - /// Gets or sets a value indicating whether data should be saved automatically. - /// + /// Gets or sets a value indicating whether data should be saved automatically. public bool AutoSave { get; set; } - /// - /// Gets or sets the behavior when a requested key is not found in the storage. - /// + /// Gets or sets the behavior when a requested key is not found in the storage. public MissingKeyBehavior MissingKeyBehavior { get; set; } = MissingKeyBehavior.ReturnDefaultValueOnly; - /// - /// Gets or sets the behavior when the type of a value associated with a key does not match the expected type. - /// + /// Gets or sets the behavior when the type of value associated with a key does not match the expected type. public TypeMismatchBehaviour TypeMismatchBehaviour { get; set; } = TypeMismatchBehaviour.OverrideValueAndType; - /// - /// Gets a value indicating whether there are unsaved changes. - /// + /// Gets a value indicating whether there are unsaved changes. public bool IsDirty { get; private set; } - /// - /// Gets a value indicating whether the storage has been disposed. - /// + /// Gets a value indicating whether the storage has been disposed. public bool IsDisposed { get; private set; } - /// - /// Initializes a new instance of the class. - /// + /// Initializes a new instance of the class. /// The file path for storing data. /// The list of supported types for storage. internal BinaryStorage(string storageFilePath, IReadOnlyList supportedTypes) @@ -53,24 +41,35 @@ internal BinaryStorage(string storageFilePath, IReadOnlyList supp _supportedTypes = supportedTypes; } + #region Events + + /// Occurs when a key is added to the storage. + public event Action OnKeyAdded; + + /// Occurs when a key is changed in the storage. + public event Action OnKeyChanged; + + /// Occurs when a key is removed from the storage. + public event Action OnKeyRemoved; + + #endregion + #region Public API - /// - /// Determines whether the specified key exists in the storage. - /// + /// Determines whether the specified key exists in the storage. /// The key to check for existence. /// True if the key exists; otherwise, false. + /// Thrown if the storage is disposed. public virtual bool Has(string key) { ThrowIfDisposed(); return _data.ContainsKey(key); } - /// - /// Gets the type of the value associated with the specified key. - /// + /// Gets the type of the value associated with the specified key. /// The key to get the type for. /// The type of the value associated with the key, or null if the key does not exist. + /// Thrown if the storage is disposed. [CanBeNull] public virtual Type TypeOf(string key) { @@ -78,11 +77,11 @@ public virtual Type TypeOf(string key) return _data.TryGetValue(key, out var record) ? record.Type : null; } - /// - /// Determines whether the storage supports the specified type. - /// + /// Determines whether the storage supports the specified type. /// The type to check for support. /// True if the type is supported; otherwise, false. + /// Thrown if the storage is disposed. + /// Thrown if the type is a collection. public virtual bool Supports() { ThrowIfDisposed(); @@ -90,43 +89,45 @@ public virtual bool Supports() return _supportedTypes.Any(c => c is TypedBinarySection); } - /// - /// Gets the value associated with the specified key. - /// + /// Gets the value associated with the specified key. /// The type of the value. /// The key to get the value for. /// The default value to use if the key does not exist. + /// Override default behavior when a requested key is not found in the storage. /// The value associated with the key. + /// Thrown if the storage is disposed. + /// Thrown if the type is a collection. + /// Thrown if the type is not registered. + /// Thrown if the type of the value associated with the key does not match the expected type. public virtual T Get(string key, T defaultValue = default, MissingKeyBehavior? overrideMissingKeyBehavior = null) { ThrowIfDisposed(); ThrowIfCollection(); var record = GetRecord(key); var missingKeyBehavior = overrideMissingKeyBehavior ?? MissingKeyBehavior; - switch (record) + return record switch { - case Record typedRecord: - return typedRecord.Value; - case not null: - throw new UnexpectedTypeException(key, nameof(Get), record.Type, typeof(T)); - case null: - return missingKeyBehavior switch - { - MissingKeyBehavior.InitializeWithDefaultValue => AddRecord(key, defaultValue).Value, - MissingKeyBehavior.ReturnDefaultValueOnly => defaultValue, - _ => throw new UnexpectedEnumException(typeof(MissingKeyBehavior), missingKeyBehavior) - }; - } + Record typedRecord => typedRecord.Value, + not null => throw new UnexpectedTypeException(key, nameof(Get), record.Type, typeof(T)), + null => missingKeyBehavior switch + { + MissingKeyBehavior.InitializeWithDefaultValue => AddRecord(key, defaultValue).Value, + MissingKeyBehavior.ReturnDefaultValueOnly => defaultValue, + _ => throw new UnexpectedEnumException(typeof(MissingKeyBehavior), missingKeyBehavior) + } + }; } - /// - /// Sets the value for the specified key. - /// + /// Sets the value for the specified key. /// The type of the value. /// The key to set the value for. /// The value to set. /// Whether to override the value if the key already exists but with another type. /// True if the value was set; otherwise, false. + /// Thrown if the storage is disposed. + /// Thrown if the type is a collection. + /// Thrown if the type is not registered. + /// Thrown if the type of the value associated with the key does not match the expected type. public virtual bool Set(string key, T value, TypeMismatchBehaviour? overrideTypeMismatchBehaviour = null) { ThrowIfDisposed(); @@ -141,7 +142,7 @@ public virtual bool Set(string key, T value, TypeMismatchBehaviour? overrideT if (record is Record typedRecord) { - return ChangeRecord(typedRecord, value); + return ChangeRecord(key, typedRecord, value); } var mismatchBehaviour = overrideTypeMismatchBehaviour ?? TypeMismatchBehaviour; @@ -208,18 +209,16 @@ public virtual int RemoveAll() return count; } - /// - /// Saves the current data to disk. - /// + /// Saves the current data to disk. + /// Thrown if the storage is disposed. public virtual void Save() { SaveDataFromDisk(); } - /// - /// Begins a scope for making multiple changes. - /// + /// Begins a scope for making multiple changes. /// An IDisposable to end the scope. + /// Thrown if the storage is disposed. public IDisposable MultipleChangeScope() { ThrowIfDisposed(); @@ -229,72 +228,67 @@ public IDisposable MultipleChangeScope() #region Collections - /// - /// Determines whether the storage supports lists of the specified type. - /// + /// Determines whether the storage supports lists of the specified type. /// The type to check for support. /// True if lists of the type are supported; otherwise, false. + /// Thrown if the storage is disposed. public virtual bool SupportsListsOf() => SupportsCollectionOf>(); - /// - /// Determines whether the storage supports sets of the specified type. - /// + /// Determines whether the storage supports sets of the specified type. /// The type to check for support. /// True if sets of the type are supported; otherwise, false. + /// Thrown if the storage is disposed. public virtual bool SupportsSetsOf() => SupportsCollectionOf>(); - /// - /// Determines whether the storage supports dictionaries of the specified key and value types. - /// + /// Determines whether the storage supports dictionaries of the specified key and value types. /// The type of the dictionary keys. /// The type of the dictionary values. /// True if dictionaries of the key and value types are supported; otherwise, false. public virtual bool SupportsDictionariesOf() => SupportsCollectionOf, ReactiveDictionary>(); - /// - /// Gets the list associated with the specified key. - /// + /// Gets the list associated with the specified key. /// The type of the list elements. /// The key to get the list for. /// The list associated with the key. + /// Thrown if the storage is disposed. + /// Thrown if the type is not registered. public IList GetListOf(string key) => GetCollectionOf>(key); - /// - /// Gets the set associated with the specified key. - /// + /// Gets the set associated with the specified key. /// The type of the set elements. /// The key to get the set for. /// The set associated with the key. + /// Thrown if the storage is disposed. + /// Thrown if the type is not registered. public ISet GetSetOf(string key) => GetCollectionOf>(key); - /// - /// Gets the dictionary associated with the specified key. - /// + /// Gets the dictionary associated with the specified key. /// The type of the dictionary keys. /// The type of the dictionary values. /// The key to get the dictionary for. /// The dictionary associated with the key. + /// Thrown if the storage is disposed. + /// Thrown if the type is not registered. public IDictionary GetDictionaryOf(string key) => GetCollectionOf, ReactiveDictionary>(key); - /// - /// Determines whether the specified collection type is supported. - /// + /// Determines whether the specified collection type is supported. /// The type of elements in the collection. /// The type of the collection. /// true if the specified collection type is supported; otherwise, false. + /// Thrown if the storage is disposed. private bool SupportsCollectionOf() where TCollection : IReactiveCollection { ThrowIfDisposed(); return _supportedTypes.Any(c => c is TypedBinarySection); } - /// - /// Gets the collection associated with the specified key. - /// + /// Gets the collection associated with the specified key. /// The type of the collection elements. /// The type of the collection. /// The key to get the collection for. /// The collection associated with the key. + /// Thrown if the storage is disposed. + /// Thrown if the type is not registered. private TCollection GetCollectionOf(string key) where TCollection : ICollection, IReactiveCollection, new() { @@ -313,13 +307,13 @@ private TCollection GetCollectionOf(string key) #region Mutable methods - /// - /// Adds a new record with the specified key and value. - /// + /// Adds a new record with the specified key and value. /// The type of the value. /// The key to add the record for. /// The value to add. /// The added record. + /// Thrown if the type is not registered. + /// Thrown if the storage is disposed. private Record AddRecord(string key, T value) { var typeIndex = _supportedTypes.FindIndex(static c => c is TypedBinarySection); @@ -334,36 +328,38 @@ private Record AddRecord(string key, T value) _data.Add(key, record); if (value is IReactiveCollection rc) { - rc.OnChanged += MarkChanged; + _collections.Add(rc, key); + rc.OnChanged += ReactiveCollectionChanged; } MarkChanged(); + OnKeyAdded?.Invoke(key); return record; } - /// - /// Changes the value of an existing record. - /// + /// Changes the value of an existing record. /// The type of the value. + /// The key to change the record for. /// The record to change. /// The new value. /// True if the value was changed; otherwise, false. - private bool ChangeRecord(Record record, T value) + /// Thrown if the storage is disposed. + private bool ChangeRecord(string key, Record record, T value) { var serializer = ((TypedBinarySection)_supportedTypes[record.TypeIndex]).Serializer; - var result = serializer.Equals(record.Value, value); - if (!result) + var equals = serializer.Equals(record.Value, value); + if (!equals) { record.Value = value; MarkChanged(); + OnKeyChanged?.Invoke(key); } - return !result; + return !equals; } - /// - /// Removes the record associated with the specified key. - /// + /// Removes the record associated with the specified key. /// The key to remove the record for. /// True if the record was removed; otherwise, false. + /// Thrown if the storage is disposed. private bool RemoveRecord(string key) { if (!_data.TryGetValue(key, out var value)) @@ -372,26 +368,28 @@ private bool RemoveRecord(string key) } if (value.Object is IReactiveCollection rc) { - rc.OnChanged -= MarkChanged; + rc.OnChanged -= ReactiveCollectionChanged; rc.Dispose(); + _collections.Remove(rc); } _supportedTypes[value.TypeIndex].Count--; _data.Remove(key); MarkChanged(); + OnKeyRemoved?.Invoke(key); return true; } - /// - /// Removes all records from the storage. - /// + /// Removes all records from the storage. + /// Thrown if the storage is disposed. private void RemoveAllRecords() { using (MultipleChangeScope()) { foreach (var rc in _data.Values.Select(c => c.Object).OfType()) { - rc.OnChanged -= MarkChanged; + rc.OnChanged -= ReactiveCollectionChanged; rc.Dispose(); + _collections.Remove(rc); } _data.Clear(); foreach (var section in _supportedTypes) @@ -406,9 +404,7 @@ private void RemoveAllRecords() #region Private methods - /// - /// Gets the record associated with the specified key. - /// + /// Gets the record associated with the specified key. /// The key to get the record for. /// The record associated with the key, or null if the key does not exist. [CanBeNull] @@ -420,6 +416,7 @@ private Record GetRecord(string key) /// /// Decreases the change scope counter and saves data if necessary. /// + /// Thrown if the storage is disposed. private void DecreaseCounter() { if (_changeScopeCounter == 0) @@ -438,9 +435,19 @@ private void DecreaseCounter() } } - /// - /// Marks the storage as changed and saves data if necessary. - /// + /// Reacts to a change in a reactive collection. + /// Thrown if the storage is disposed. + private void ReactiveCollectionChanged(IReactiveCollection collection) + { + if (_collections.TryGetValue(collection, out var key)) + { + MarkChanged(); + OnKeyChanged?.Invoke(key); + } + } + + /// Marks the storage as changed and saves data if necessary. + /// Thrown if the storage is disposed. private void MarkChanged() { if (!AutoSave || _changeScopeCounter > 0) @@ -451,21 +458,17 @@ private void MarkChanged() SaveDataFromDisk(); } - /// - /// Throws an exception if the storage has been disposed. - /// - /// Thrown if the storage is disposed. + /// Throws an exception if the storage has been disposed. + /// Thrown if the storage is disposed. private void ThrowIfDisposed() { if (IsDisposed) { - throw new StorageDisposedException(_storageFilePath); + throw new ObjectDisposedException($"{nameof(BinaryStorage)}: {_storageFilePath}"); } } - /// - /// Throws an exception if the specified type is a collection. - /// + /// Throws an exception if the specified type is a collection. /// The type to check. /// Thrown if the type is a collection. private void ThrowIfCollection() @@ -481,26 +484,20 @@ private void ThrowIfCollection() #region Dispose Pattern - /// - /// Finalizer - /// + /// Finalizer ~BinaryStorage() { Dispose(false); } - /// - /// Disposes the resources used by the storage. - /// + /// Disposes the resources used by the storage. public virtual void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - /// - /// Disposes the resources used by the storage. - /// + /// Disposes the resources used by the storage. /// Whether managed resources should be disposed. private void Dispose(bool disposing) { @@ -512,10 +509,15 @@ private void Dispose(bool disposing) // Always dispose IReactiveCollection instances foreach (var rc in _data.Values.Select(c => c.Object).OfType()) { - rc.OnChanged -= MarkChanged; + rc.OnChanged -= ReactiveCollectionChanged; rc.Dispose(); + _collections.Remove(rc); } + OnKeyAdded = null; + OnKeyChanged = null; + OnKeyRemoved = null; + if (disposing) { _data.Clear(); @@ -530,22 +532,26 @@ private void Dispose(bool disposing) #region File System IO - /// - /// Loads the data from disk into memory. - /// + /// Loads the data from disk into memory. + /// Thrown if the storage is disposed. + /// An I/O error occurred private void LoadDataFromDisk() { ThrowIfDisposed(); BinaryStorageIO.LoadDataFromDisk(_storageFilePath, _supportedTypes, _data); - foreach (var rc in _data.Values.Select(c => c.Object).OfType()) + foreach (var pair in _data) { - rc.OnChanged += MarkChanged; + if (pair.Value.Object is IReactiveCollection rc) + { + _collections.Add(rc, pair.Key); + rc.OnChanged += ReactiveCollectionChanged; + } } } - /// - /// Saves the data from memory to disk. - /// + /// Saves the data from memory to disk. + /// Thrown if the storage is disposed. + /// An I/O error occurred private void SaveDataFromDisk() { ThrowIfDisposed(); diff --git a/Runtime/BinaryStorageIO.cs b/Runtime/BinaryStorageIO.cs index fd16574..81b7629 100644 --- a/Runtime/BinaryStorageIO.cs +++ b/Runtime/BinaryStorageIO.cs @@ -8,6 +8,11 @@ namespace Appegy.Storage { internal static class BinaryStorageIO { + /// Save data from memory to disk. + /// Path to the storage file + /// List of sections + /// Dictionary to store data + /// An I/O error occurred internal static void SaveDataOnDisk(string storageFilePath, IReadOnlyList sections, IReadOnlyDictionary data) { // make sure there is no temp file from previous (most likely failed) save try @@ -80,6 +85,11 @@ internal static void SaveDataOnDisk(string storageFilePath, IReadOnlyList Load data from disk to memory. + /// Path to the storage file + /// List of sections + /// Dictionary to store data + /// An I/O error occurred internal static void LoadDataFromDisk(string storageFilePath, IReadOnlyList sections, IDictionary data) { data.Clear(); diff --git a/Runtime/Collections/IReactiveCollection.cs b/Runtime/Collections/IReactiveCollection.cs index 9a4a0cb..95d7501 100644 --- a/Runtime/Collections/IReactiveCollection.cs +++ b/Runtime/Collections/IReactiveCollection.cs @@ -4,6 +4,6 @@ namespace Appegy.Storage { internal interface IReactiveCollection : IDisposable { - public event Action OnChanged; + public event Action OnChanged; } } \ No newline at end of file diff --git a/Runtime/Collections/ReactiveDictionary.cs b/Runtime/Collections/ReactiveDictionary.cs index 26639e1..cd84ba8 100644 --- a/Runtime/Collections/ReactiveDictionary.cs +++ b/Runtime/Collections/ReactiveDictionary.cs @@ -4,24 +4,24 @@ namespace Appegy.Storage { - public class ReactiveDictionary : IReactiveCollection, IDictionary, IReadOnlyDictionary + internal class ReactiveDictionary : IReactiveCollection, IDictionary, IReadOnlyDictionary { private readonly Dictionary _dictionary = new(); public bool IsDisposed { get; private set; } - public event Action OnChanged; + public event Action OnChanged; private void SetDirty() { - OnChanged?.Invoke(); + OnChanged?.Invoke(this); } private void ThrowIfDisposed() { if (IsDisposed) { - throw new CollectionDisposedException(); + throw new ObjectDisposedException(nameof(ReactiveDictionary)); } } diff --git a/Runtime/Collections/ReactiveList.cs b/Runtime/Collections/ReactiveList.cs index 55f977c..0db5d3c 100644 --- a/Runtime/Collections/ReactiveList.cs +++ b/Runtime/Collections/ReactiveList.cs @@ -4,24 +4,24 @@ namespace Appegy.Storage { - public class ReactiveList : IReactiveCollection, IList, IReadOnlyList + internal class ReactiveList : IReactiveCollection, IList, IReadOnlyList { private readonly List _list = new(); public bool IsDisposed { get; private set; } - public event Action OnChanged; + public event Action OnChanged; private void SetDirty() { - OnChanged?.Invoke(); + OnChanged?.Invoke(this); } private void ThrowIfDisposed() { if (IsDisposed) { - throw new CollectionDisposedException(); + throw new ObjectDisposedException(nameof(ReactiveList)); } } diff --git a/Runtime/Collections/ReactiveSet.cs b/Runtime/Collections/ReactiveSet.cs index d5a2be6..d5ecda1 100644 --- a/Runtime/Collections/ReactiveSet.cs +++ b/Runtime/Collections/ReactiveSet.cs @@ -5,24 +5,24 @@ namespace Appegy.Storage { - public class ReactiveSet : IReactiveCollection, ISet, IReadOnlyCollection + internal class ReactiveSet : IReactiveCollection, ISet, IReadOnlyCollection { private readonly HashSet _set = new(); public bool IsDisposed { get; private set; } - public event Action OnChanged; + public event Action OnChanged; private void SetDirty() { - OnChanged?.Invoke(); + OnChanged?.Invoke(this); } private void ThrowIfDisposed() { if (IsDisposed) { - throw new CollectionDisposedException(); + throw new ObjectDisposedException(nameof(ReactiveSet)); } } diff --git a/Runtime/Exceptions/CollectionDisposedException.cs b/Runtime/Exceptions/CollectionDisposedException.cs deleted file mode 100644 index 12086e3..0000000 --- a/Runtime/Exceptions/CollectionDisposedException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Appegy.Storage -{ - public class CollectionDisposedException : Exception - { - public CollectionDisposedException() - : base("Collection already disposed and can't be used anymore.") - { - } - } -} \ No newline at end of file diff --git a/Runtime/Exceptions/CollectionDisposedException.cs.meta b/Runtime/Exceptions/CollectionDisposedException.cs.meta deleted file mode 100644 index dad2af2..0000000 --- a/Runtime/Exceptions/CollectionDisposedException.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: bcd1185209924629ab8d16c26a98be27 -timeCreated: 1718201930 \ No newline at end of file diff --git a/Runtime/Exceptions/StorageDisposedException.cs b/Runtime/Exceptions/StorageDisposedException.cs deleted file mode 100644 index 59f3b7e..0000000 --- a/Runtime/Exceptions/StorageDisposedException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Appegy.Storage -{ - public class StorageDisposedException : Exception - { - public StorageDisposedException(string storageFilePath) - : base($"Storage already disposed and can't be used anymore. File path: {storageFilePath}") - { - } - } -} \ No newline at end of file diff --git a/Runtime/Exceptions/StorageDisposedException.cs.meta b/Runtime/Exceptions/StorageDisposedException.cs.meta deleted file mode 100644 index ff1ad0a..0000000 --- a/Runtime/Exceptions/StorageDisposedException.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 0dc468d1621f4470b4e1840e203e57dc -timeCreated: 1694037130 \ No newline at end of file diff --git a/Runtime/Utilities/CollectionExtensions.cs b/Runtime/Utilities/CollectionExtensions.cs index 2fad70d..1b16f1b 100644 --- a/Runtime/Utilities/CollectionExtensions.cs +++ b/Runtime/Utilities/CollectionExtensions.cs @@ -5,6 +5,63 @@ namespace Appegy.Storage { internal static class CollectionExtensions { + #region AddRange + + public static void AddRange(this ICollection source, T item1, T item2) + { + source.Add(item1); + source.Add(item2); + } + + public static void AddRange(this ICollection source, T item1, T item2, T item3) + { + source.Add(item1); + source.Add(item2); + source.Add(item3); + } + + public static void AddRange(this ICollection source, T item1, T item2, T item3, T item4) + { + source.Add(item1); + source.Add(item2); + source.Add(item3); + source.Add(item4); + } + + public static void AddRange(this ICollection source, params T[] items) + { + items.ForEach(source.Add); + } + + public static void AddRange(this IDictionary source, (TKey Key, TValue Value) item1, (TKey Key, TValue Value) item2) + { + source.Add(item1.Key, item1.Value); + source.Add(item2.Key, item2.Value); + } + + public static void AddRange(this IDictionary source, (TKey Key, TValue Value) item1, (TKey Key, TValue Value) item2, (TKey Key, TValue Value) item3) + { + source.Add(item1.Key, item1.Value); + source.Add(item2.Key, item2.Value); + source.Add(item3.Key, item3.Value); + } + + public static void AddRange(this IDictionary source, (TKey Key, TValue Value) item1, (TKey Key, TValue Value) item2, (TKey Key, TValue Value) item3, + (TKey Key, TValue Value) item4) + { + source.Add(item1.Key, item1.Value); + source.Add(item2.Key, item2.Value); + source.Add(item3.Key, item3.Value); + source.Add(item4.Key, item4.Value); + } + + public static void AddRange(this IDictionary source, params (TKey Key, TValue Value)[] items) + { + items.ForEach(item => source.Add(item.Key, item.Value)); + } + + #endregion + public static bool IsCollection(this Type type) { if (!type.IsGenericType) @@ -15,6 +72,14 @@ public static bool IsCollection(this Type type) return typeof(ICollection<>).IsAssignableFrom(genericTypeDefinition); } + public static void ForEach(this Span source, Action predicate) + { + foreach (var item in source) + { + predicate(item); + } + } + public static void ForEach(this IEnumerable source, Action predicate) { foreach (var item in source) diff --git a/Tests/BinaryStorageTests.cs b/Tests/BinaryStorageTests.cs index a2dcefa..c33f167 100644 --- a/Tests/BinaryStorageTests.cs +++ b/Tests/BinaryStorageTests.cs @@ -99,7 +99,7 @@ public void WhenStorageDisposed_AndHasCalled_ThenExceptionOccured() // Assert Action action = () => storage.Has("key"); - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -115,7 +115,7 @@ public void WhenStorageDisposed_AndTypeOfCalled_ThenExceptionOccured() // Assert Action action = () => storage.TypeOf("key"); - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -131,7 +131,7 @@ public void WhenStorageDisposed_AndSupportsCalled_ThenExceptionOccured() // Assert Action action = () => storage.Supports(); - action.Should().Throw(); + action.Should().Throw(); } #region Reactive Lists @@ -389,6 +389,129 @@ public void WhenReactiveDictionaryChanged_AndStorageReloaded_ThenValuesInStorage #endregion + #region Events + + [Test] + public void WhenKeyAddedToStorage_ThenOnKeyAddedEventRaised() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddTypeSerializer(Int32Serializer.Shared) + .Build(); + + var raised = false; + storage.OnKeyAdded += s => { raised = s == "key"; }; + + // Act + storage.Set("key", 10); + + // Assert + raised.Should().BeTrue("OnKeyAdded should be raised when Set is called."); + } + + [Test] + public void WhenKeyRemovedFromStorage_ThenOnKeyRemovedEventRaised() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddTypeSerializer(Int32Serializer.Shared) + .Build(); + + storage.Set("key", 10); + + var raised = false; + storage.OnKeyRemoved += s => { raised = s == "key"; }; + + // Act + storage.Remove("key"); + + // Assert + raised.Should().BeTrue("OnKeyRemoved should be raised when Remove is called."); + } + + [Test] + public void WhenKeyChangedInStorage_ThenOnKeyChangedEventRaised() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddTypeSerializer(Int32Serializer.Shared) + .Build(); + + storage.Set("key", 10); + + var raised = false; + storage.OnKeyChanged += s => { raised = s == "key"; }; + + // Act + storage.Set("key", 20); + + // Assert + raised.Should().BeTrue("OnKeyChanged should be raised when Set is called."); + } + + [Test] + public void WhenCollectionAddedToStorage_ThenOnKeyAddedEventRaised() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddTypeSerializer(Int32Serializer.Shared) + .SupportListsOf() + .Build(); + + var raised = false; + storage.OnKeyAdded += s => { raised = s == "key"; }; + + // Act + storage.GetListOf("key").Add(10); + + // Assert + raised.Should().BeTrue("OnKeyAdded should be raised when Set is called."); + } + + [Test] + public void WhenCollectionRemovedFromStorage_ThenOnKeyRemovedEventRaised() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddTypeSerializer(Int32Serializer.Shared) + .SupportListsOf() + .Build(); + + storage.GetListOf("key").Add(10); + + var raised = false; + storage.OnKeyRemoved += s => { raised = s == "key"; }; + + // Act + storage.Remove("key"); + + // Assert + raised.Should().BeTrue("OnKeyRemoved should be raised when Remove is called."); + } + + [Test] + public void WhenCollectionChangedInStorage_ThenOnKeyChangedEventRaised() + { + // Arrange + using var storage = BinaryStorage.Construct(StoragePath) + .AddTypeSerializer(Int32Serializer.Shared) + .SupportListsOf() + .Build(); + + storage.GetListOf("key").Add(10); + + var raised = false; + storage.OnKeyChanged += s => { raised = s == "key"; }; + + // Act + storage.GetListOf("key").Add(20); + + // Assert + raised.Should().BeTrue("OnKeyChanged should be raised when Set is called."); + } + + #endregion + #region TypeMismatchBehaviour Tests [Test] diff --git a/Tests/CollectionTests/ReactiveDictionaryTests.cs b/Tests/CollectionTests/ReactiveDictionaryTests.cs index 274f6c5..4dcc785 100644 --- a/Tests/CollectionTests/ReactiveDictionaryTests.cs +++ b/Tests/CollectionTests/ReactiveDictionaryTests.cs @@ -12,7 +12,7 @@ public class ReactiveDictionaryTests public void WhenItemIsAdded_AndDictionaryIsNotDisposed_ThenItemShouldBeInDictionary() { // Arrange - var dictionary = new ReactiveDictionary(); + using var dictionary = new ReactiveDictionary(); // Act dictionary.Add(1, "one"); @@ -25,7 +25,8 @@ public void WhenItemIsAdded_AndDictionaryIsNotDisposed_ThenItemShouldBeInDiction public void WhenItemIsRemoved_AndItemExistsInDictionary_ThenItemShouldNotBeInDictionary() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" }, { 2, "two" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.AddRange((1, "one"), (2, "two")); // Act dictionary.Remove(2); @@ -38,7 +39,8 @@ public void WhenItemIsRemoved_AndItemExistsInDictionary_ThenItemShouldNotBeInDic public void WhenClearIsCalled_AndDictionaryHasItems_ThenDictionaryShouldBeEmpty() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" }, { 2, "two" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.AddRange((1, "one"), (2, "two")); // Act dictionary.Clear(); @@ -51,7 +53,8 @@ public void WhenClearIsCalled_AndDictionaryHasItems_ThenDictionaryShouldBeEmpty( public void WhenGettingItemByKey_AndKeyIsValid_ThenShouldReturnCorrectItem() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" }, { 2, "two" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.AddRange((1, "one"), (2, "two")); // Act var item = dictionary[1]; @@ -64,7 +67,8 @@ public void WhenGettingItemByKey_AndKeyIsValid_ThenShouldReturnCorrectItem() public void WhenSettingItemByKey_AndKeyIsValid_ThenShouldUpdateItem() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" }, { 2, "two" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.AddRange((1, "one"), (2, "two")); // Act dictionary[1] = "uno"; @@ -84,7 +88,7 @@ public void WhenItemIsAdded_AndDictionaryIsDisposed_ThenShouldThrowException() Action action = () => dictionary.Add(1, "one"); // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -98,7 +102,7 @@ public void WhenItemIsRemoved_AndDictionaryIsDisposed_ThenShouldThrowException() Action action = () => dictionary.Remove(1); // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -112,7 +116,7 @@ public void WhenClearIsCalled_AndDictionaryIsDisposed_ThenShouldThrowException() Action action = () => dictionary.Clear(); // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -126,7 +130,7 @@ public void WhenSetItemByKey_AndDictionaryIsDisposed_ThenShouldThrowException() Action action = () => dictionary[1] = "uno"; // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -159,9 +163,9 @@ public void WhenDictionaryIsDisposed_ThenDictionaryShouldBeEmpty() public void WhenOnChangedIsSubscribed_AndDictionaryIsModified_ThenOnChangedShouldBeTriggered() { // Arrange - var dictionary = new ReactiveDictionary(); + using var dictionary = new ReactiveDictionary(); var wasTriggered = false; - dictionary.OnChanged += () => wasTriggered = true; + dictionary.OnChanged += (_) => wasTriggered = true; // Act dictionary.Add(1, "one"); @@ -174,9 +178,10 @@ public void WhenOnChangedIsSubscribed_AndDictionaryIsModified_ThenOnChangedShoul public void WhenOnChangedIsSubscribed_AndDictionaryIsCleared_ThenOnChangedShouldBeTriggered() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" }, { 2, "two" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.AddRange((1, "one"), (2, "two")); var wasTriggered = false; - dictionary.OnChanged += () => wasTriggered = true; + dictionary.OnChanged += (_) => wasTriggered = true; // Act dictionary.Clear(); @@ -191,7 +196,7 @@ public void WhenOnChangedIsSubscribed_AndDictionaryIsDisposed_ThenOnChangedShoul // Arrange var dictionary = new ReactiveDictionary { { 1, "one" }, { 2, "two" } }; var wasTriggered = false; - dictionary.OnChanged += () => wasTriggered = true; + dictionary.OnChanged += (_) => wasTriggered = true; // Act dictionary.Dispose(); @@ -204,7 +209,8 @@ public void WhenOnChangedIsSubscribed_AndDictionaryIsDisposed_ThenOnChangedShoul public void WhenKeyExists_AndTryGetValueIsCalled_ThenShouldReturnTrueAndCorrectValue() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.Add(1, "one"); // Act var result = dictionary.TryGetValue(1, out var value); @@ -218,7 +224,7 @@ public void WhenKeyExists_AndTryGetValueIsCalled_ThenShouldReturnTrueAndCorrectV public void WhenKeyDoesNotExist_AndTryGetValueIsCalled_ThenShouldReturnFalse() { // Arrange - var dictionary = new ReactiveDictionary(); + using var dictionary = new ReactiveDictionary(); // Act var result = dictionary.TryGetValue(1, out var value); @@ -232,7 +238,8 @@ public void WhenKeyDoesNotExist_AndTryGetValueIsCalled_ThenShouldReturnFalse() public void WhenContainsKeyIsCalled_AndKeyExists_ThenShouldReturnTrue() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.Add(1, "one"); // Act var result = dictionary.ContainsKey(1); @@ -245,7 +252,7 @@ public void WhenContainsKeyIsCalled_AndKeyExists_ThenShouldReturnTrue() public void WhenContainsKeyIsCalled_AndKeyDoesNotExist_ThenShouldReturnFalse() { // Arrange - var dictionary = new ReactiveDictionary(); + using var dictionary = new ReactiveDictionary(); // Act var result = dictionary.ContainsKey(1); @@ -258,7 +265,8 @@ public void WhenContainsKeyIsCalled_AndKeyDoesNotExist_ThenShouldReturnFalse() public void WhenCopyToIsCalled_ThenDictionaryShouldBeCopiedToArray() { // Arrange - var dictionary = new ReactiveDictionary { { 1, "one" }, { 2, "two" } }; + using var dictionary = new ReactiveDictionary(); + dictionary.AddRange((1, "one"), (2, "two")); var array = new KeyValuePair[2]; // Act @@ -269,4 +277,4 @@ public void WhenCopyToIsCalled_ThenDictionaryShouldBeCopiedToArray() array.Should().Contain(new KeyValuePair(2, "two")); } } -} +} \ No newline at end of file diff --git a/Tests/CollectionTests/ReactiveListTests.cs b/Tests/CollectionTests/ReactiveListTests.cs index 3a002c5..bc7d970 100644 --- a/Tests/CollectionTests/ReactiveListTests.cs +++ b/Tests/CollectionTests/ReactiveListTests.cs @@ -11,7 +11,7 @@ public class ReactiveListTests public void WhenItemIsAdded_AndListIsNotDisposed_ThenItemShouldBeInList() { // Arrange - var list = new ReactiveList(); + using var list = new ReactiveList(); // Act list.Add(1); @@ -24,7 +24,8 @@ public void WhenItemIsAdded_AndListIsNotDisposed_ThenItemShouldBeInList() public void WhenItemIsRemoved_AndItemExistsInList_ThenItemShouldNotBeInList() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); // Act list.Remove(2); @@ -37,7 +38,8 @@ public void WhenItemIsRemoved_AndItemExistsInList_ThenItemShouldNotBeInList() public void WhenClearIsCalled_AndListHasItems_ThenListShouldBeEmpty() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); // Act list.Clear(); @@ -50,7 +52,8 @@ public void WhenClearIsCalled_AndListHasItems_ThenListShouldBeEmpty() public void WhenGettingItemByIndex_AndIndexIsValid_ThenShouldReturnCorrectItem() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); // Act var item = list[1]; @@ -63,7 +66,8 @@ public void WhenGettingItemByIndex_AndIndexIsValid_ThenShouldReturnCorrectItem() public void WhenSettingItemByIndex_AndIndexIsValid_ThenShouldUpdateItem() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); // Act list[1] = 5; @@ -76,7 +80,8 @@ public void WhenSettingItemByIndex_AndIndexIsValid_ThenShouldUpdateItem() public void WhenItemIsInserted_AndIndexIsValid_ThenShouldInsertItemAtCorrectPosition() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); // Act list.Insert(1, 5); @@ -90,7 +95,8 @@ public void WhenItemIsInserted_AndIndexIsValid_ThenShouldInsertItemAtCorrectPosi public void WhenItemIsRemovedByIndex_AndIndexIsValid_ThenShouldRemoveItemAtCorrectPosition() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); // Act list.RemoveAt(1); @@ -136,7 +142,7 @@ public void WhenItemIsAdded_AndListIsDisposed_ThenShouldThrowException() Action action = () => list.Add(1); // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -147,10 +153,10 @@ public void WhenGettingItemByIndex_AndListIsDisposed_ThenShouldThrowException() list.Dispose(); // Act - Action action = () => { var item = list[1]; }; + Action action = () => { _ = list[1]; }; // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -164,7 +170,7 @@ public void WhenSettingItemByIndex_AndListIsDisposed_ThenShouldThrowException() Action action = () => list[1] = 5; // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -173,7 +179,7 @@ public void WhenItemIsAdded_AndListIsNotDisposed_ThenOnChangedShouldBeTriggered( // Arrange var list = new ReactiveList(); var wasTriggered = false; - list.OnChanged += () => wasTriggered = true; + list.OnChanged += (_) => wasTriggered = true; // Act list.Add(1); @@ -186,9 +192,10 @@ public void WhenItemIsAdded_AndListIsNotDisposed_ThenOnChangedShouldBeTriggered( public void WhenItemIsRemoved_AndListIsNotDisposed_ThenOnChangedShouldBeTriggered() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); var wasTriggered = false; - list.OnChanged += () => wasTriggered = true; + list.OnChanged += (_) => wasTriggered = true; // Act list.Remove(2); @@ -201,9 +208,10 @@ public void WhenItemIsRemoved_AndListIsNotDisposed_ThenOnChangedShouldBeTriggere public void WhenListIsCleared_AndListIsNotDisposed_ThenOnChangedShouldBeTriggered() { // Arrange - var list = new ReactiveList { 1, 2, 3 }; + using var list = new ReactiveList(); + list.AddRange(1, 2, 3); var wasTriggered = false; - list.OnChanged += () => wasTriggered = true; + list.OnChanged += (_) => wasTriggered = true; // Act list.Clear(); @@ -212,4 +220,4 @@ public void WhenListIsCleared_AndListIsNotDisposed_ThenOnChangedShouldBeTriggere wasTriggered.Should().BeTrue(); } } -} +} \ No newline at end of file diff --git a/Tests/CollectionTests/ReactiveSetTests.cs b/Tests/CollectionTests/ReactiveSetTests.cs index 9b95e6b..d2cf8d8 100644 --- a/Tests/CollectionTests/ReactiveSetTests.cs +++ b/Tests/CollectionTests/ReactiveSetTests.cs @@ -11,7 +11,7 @@ public class ReactiveSetTests public void WhenItemIsAdded_AndSetIsNotDisposed_ThenItemShouldBeInSet() { // Arrange - var set = new ReactiveSet(); + using var set = new ReactiveSet(); // Act set.Add(1); @@ -24,7 +24,8 @@ public void WhenItemIsAdded_AndSetIsNotDisposed_ThenItemShouldBeInSet() public void WhenItemIsRemoved_AndItemExistsInSet_ThenItemShouldNotBeInSet() { // Arrange - var set = new ReactiveSet { 1, 2, 3 }; + using var set = new ReactiveSet(); + set.AddRange(1, 2, 3); // Act set.Remove(2); @@ -37,7 +38,8 @@ public void WhenItemIsRemoved_AndItemExistsInSet_ThenItemShouldNotBeInSet() public void WhenClearIsCalled_AndSetHasItems_ThenSetShouldBeEmpty() { // Arrange - var set = new ReactiveSet { 1, 2, 3 }; + using var set = new ReactiveSet(); + set.AddRange(1, 2, 3); // Act set.Clear(); @@ -57,7 +59,7 @@ public void WhenItemIsAdded_AndSetIsDisposed_ThenShouldThrowException() Action action = () => set.Add(1); // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -71,7 +73,7 @@ public void WhenItemIsRemoved_AndSetIsDisposed_ThenShouldThrowException() Action action = () => set.Remove(1); // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] @@ -85,14 +87,15 @@ public void WhenClearIsCalled_AndSetIsDisposed_ThenShouldThrowException() Action action = () => set.Clear(); // Assert - action.Should().Throw(); + action.Should().Throw(); } [Test] public void WhenExceptWithIsCalled_AndSetIsNotDisposed_ThenShouldRemoveItems() { // Arrange - var set = new ReactiveSet { 1, 2, 3 }; + using var set = new ReactiveSet(); + set.AddRange(1, 2, 3); // Act set.ExceptWith(new[] { 2, 3, 4 }); @@ -106,7 +109,8 @@ public void WhenExceptWithIsCalled_AndSetIsNotDisposed_ThenShouldRemoveItems() public void WhenIntersectWithIsCalled_AndSetIsNotDisposed_ThenShouldRetainOnlyCommonItems() { // Arrange - var set = new ReactiveSet { 1, 2, 3 }; + using var set = new ReactiveSet(); + set.AddRange(1, 2, 3); // Act set.IntersectWith(new[] { 2, 3, 4 }); @@ -120,7 +124,8 @@ public void WhenIntersectWithIsCalled_AndSetIsNotDisposed_ThenShouldRetainOnlyCo public void WhenSymmetricExceptWithIsCalled_AndSetIsNotDisposed_ThenShouldRetainUniqueItems() { // Arrange - var set = new ReactiveSet { 1, 2, 3 }; + using var set = new ReactiveSet(); + set.AddRange(1, 2, 3); // Act set.SymmetricExceptWith(new[] { 2, 3, 4 }); @@ -134,7 +139,8 @@ public void WhenSymmetricExceptWithIsCalled_AndSetIsNotDisposed_ThenShouldRetain public void WhenUnionWithIsCalled_AndSetIsNotDisposed_ThenShouldIncludeAllUniqueItems() { // Arrange - var set = new ReactiveSet { 1, 2, 3 }; + using var set = new ReactiveSet(); + set.AddRange(1, 2, 3); // Act set.UnionWith(new[] { 2, 3, 4 }); @@ -173,9 +179,9 @@ public void WhenSetIsDisposed_ThenSetShouldBeEmpty() public void WhenOnChangedIsSubscribed_AndSetIsModified_ThenOnChangedShouldBeTriggered() { // Arrange - var set = new ReactiveSet(); + using var set = new ReactiveSet(); var wasTriggered = false; - set.OnChanged += () => wasTriggered = true; + set.OnChanged += (_) => wasTriggered = true; // Act set.Add(1); @@ -188,9 +194,10 @@ public void WhenOnChangedIsSubscribed_AndSetIsModified_ThenOnChangedShouldBeTrig public void WhenOnChangedIsSubscribed_AndSetIsCleared_ThenOnChangedShouldBeTriggered() { // Arrange - var set = new ReactiveSet { 1, 2, 3 }; + using var set = new ReactiveSet(); + set.AddRange(1, 2, 3); var wasTriggered = false; - set.OnChanged += () => wasTriggered = true; + set.OnChanged += (_) => wasTriggered = true; // Act set.Clear(); @@ -205,7 +212,7 @@ public void WhenOnChangedIsSubscribed_AndSetIsDisposed_ThenOnChangedShouldBeTrig // Arrange var set = new ReactiveSet { 1, 2, 3 }; var wasTriggered = false; - set.OnChanged += () => wasTriggered = true; + set.OnChanged += (_) => wasTriggered = true; // Act set.Dispose(); @@ -214,4 +221,4 @@ public void WhenOnChangedIsSubscribed_AndSetIsDisposed_ThenOnChangedShouldBeTrig wasTriggered.Should().BeTrue(); } } -} +} \ No newline at end of file