From eeb0e0026a1832fee7d7bbfb2cc670bc9c7a802e Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Sat, 20 Apr 2013 19:47:30 -0200 Subject: [PATCH] Making PropertyCollection, PropertyGroupCollection and PropertyTypeCollection return a proper null when FirstOrDefault should return null. --- src/Umbraco.Core/Models/EntityBase/Entity.cs | 13 - .../Collections/PropertyCollectionTests.cs | 423 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 3 files changed, 424 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index 8df5be5809..50d7fec194 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -168,19 +168,6 @@ namespace Umbraco.Core.Models.EntityBase } } - public static bool operator ==(Entity left, Entity right) - { - if (ReferenceEquals(null, left)) - return false; - - return left.Equals(right); - } - - public static bool operator !=(Entity left, Entity right) - { - return !(left == right); - } - public virtual bool SameIdentityAs(IEntity other) { if (ReferenceEquals(null, other)) diff --git a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs new file mode 100644 index 0000000000..0699370259 --- /dev/null +++ b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs @@ -0,0 +1,423 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Models.Collections +{ + [TestFixture] + public class PropertyCollectionTests + { + [Test] + public void SimpleOrder_Returns_Null_On_FirstOrDefault_When_Empty() + { + var orders = new SimpleOrder(); + var item = orders.FirstOrDefault(); + + Assert.That(item == null, Is.True); + } + + [Test] + public void PropertyCollection_Returns_Null_On_FirstOrDefault_When_Empty() + { + var list = new List(); + var collection = new PropertyCollection(list); + + var first = collection.FirstOrDefault(); + var second = collection.FirstOrDefault(x => x.Alias.InvariantEquals("Test")); + + Assert.That(first, Is.Null); + Assert.That(first == null, Is.True); + Assert.That(second == null, Is.True); + } + + [Test] + public void PropertyTypeCollection_Returns_Null_On_FirstOrDefault_When_Empty() + { + var list = new List(); + var collection = new PropertyTypeCollection(list); + + Assert.That(collection.FirstOrDefault(), Is.Null); + Assert.That(collection.FirstOrDefault(x => x.Alias.InvariantEquals("Test")) == null, Is.True); + } + + [Test] + public void PropertyGroupCollection_Returns_Null_On_FirstOrDefault_When_Empty() + { + var list = new List(); + var collection = new PropertyGroupCollection(list); + + Assert.That(collection.FirstOrDefault(), Is.Null); + Assert.That(collection.FirstOrDefault(x => x.Name.InvariantEquals("Test")) == null, Is.True); + } + + [Test] + public void PropertyGroups_Collection_FirstOrDefault_Returns_Null() + { + var contentType = MockedContentTypes.CreateTextpageContentType(); + + Assert.That(contentType.PropertyGroups, Is.Not.Null); + Assert.That(contentType.PropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals("Content")) == null, Is.False); + Assert.That(contentType.PropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals("Test")) == null, Is.True); + Assert.That(contentType.PropertyGroups.Any(x => x.Name.InvariantEquals("Test")), Is.False); + } + } + + public class SimpleOrder : KeyedCollection, INotifyCollectionChanged + { + // The parameterless constructor of the base class creates a + // KeyedCollection with an internal dictionary. For this code + // example, no other constructors are exposed. + // + public SimpleOrder() : base() { } + + public SimpleOrder(IEnumerable properties) + { + Reset(properties); + } + + // This is the only method that absolutely must be overridden, + // because without it the KeyedCollection cannot extract the + // keys from the items. The input parameter type is the + // second generic type argument, in this case OrderItem, and + // the return value type is the first generic type argument, + // in this case int. + // + protected override int GetKeyForItem(OrderItem item) + { + // In this example, the key is the part number. + return item.PartNumber; + } + + internal void Reset(IEnumerable properties) + { + Clear(); + properties.ForEach(Add); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + protected override void SetItem(int index, OrderItem item) + { + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + protected override void RemoveItem(int index) + { + var removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + protected override void InsertItem(int index, OrderItem item) + { + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public new bool Contains(int partNumber) + { + return this.Any(x => x.PartNumber == partNumber); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + if (CollectionChanged != null) + { + CollectionChanged(this, args); + } + } + } + + public class OrderItem : Item + { + public readonly int PartNumber; + public readonly string Description; + public readonly double UnitPrice; + + private int _quantity = 0; + + public OrderItem(int partNumber, string description, + int quantity, double unitPrice) + { + this.PartNumber = partNumber; + this.Description = description; + this.Quantity = quantity; + this.UnitPrice = unitPrice; + } + + public int Quantity + { + get { return _quantity; } + set + { + if (value < 0) + throw new ArgumentException("Quantity cannot be negative."); + + _quantity = value; + } + } + + public override string ToString() + { + return String.Format( + "{0,9} {1,6} {2,-12} at {3,8:#,###.00} = {4,10:###,###.00}", + PartNumber, _quantity, Description, UnitPrice, + UnitPrice * _quantity); + } + } + + public abstract class Item : IEntity, ICanBeDirty + { + private bool _hasIdentity; + private int? _hash; + private int _id; + private Guid _key; + + protected Item() + { + _propertyChangedInfo = new Dictionary(); + } + + /// + /// Integer Id + /// + [DataMember] + public int Id + { + get + { + return _id; + } + set + { + _id = value; + HasIdentity = true; + } + } + + /// + /// Guid based Id + /// + /// The key is currectly used to store the Unique Id from the + /// umbracoNode table, which many of the entities are based on. + [DataMember] + public Guid Key + { + get + { + if (_key == Guid.Empty) + return _id.ToGuid(); + + return _key; + } + set { _key = value; } + } + + /// + /// Gets or sets the Created Date + /// + [DataMember] + public DateTime CreateDate { get; set; } + + /// + /// Gets or sets the Modified Date + /// + [DataMember] + public DateTime UpdateDate { get; set; } + + /// + /// Gets or sets the WasCancelled flag, which is used to track + /// whether some action against an entity was cancelled through some event. + /// This only exists so we have a way to check if an event was cancelled through + /// the new api, which also needs to take effect in the legacy api. + /// + [IgnoreDataMember] + internal bool WasCancelled { get; set; } + + /// + /// Property changed event + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Method to call on a property setter. + /// + /// The property info. + protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) + { + _propertyChangedInfo[propertyInfo.Name] = true; + + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); + } + } + + internal virtual void ResetIdentity() + { + _hasIdentity = false; + _id = default(int); + } + + /// + /// Method to call on entity saved when first added + /// + internal virtual void AddingEntity() + { + CreateDate = DateTime.Now; + UpdateDate = DateTime.Now; + } + + /// + /// Method to call on entity saved/updated + /// + internal virtual void UpdatingEntity() + { + UpdateDate = DateTime.Now; + } + + /// + /// Tracks the properties that have changed + /// + //private readonly IDictionary _propertyChangedInfo = new Dictionary(); + private IDictionary _propertyChangedInfo; + + /// + /// Indicates whether a specific property on the current entity is dirty. + /// + /// Name of the property to check + /// True if Property is dirty, otherwise False + public virtual bool IsPropertyDirty(string propertyName) + { + return _propertyChangedInfo.Any(x => x.Key == propertyName); + } + + /// + /// Indicates whether the current entity is dirty. + /// + /// True if entity is dirty, otherwise False + public virtual bool IsDirty() + { + return _propertyChangedInfo.Any(); + } + + /// + /// Resets dirty properties by clearing the dictionary used to track changes. + /// + /// + /// Please note that resetting the dirty properties could potentially + /// obstruct the saving of a new or updated entity. + /// + public virtual void ResetDirtyProperties() + { + _propertyChangedInfo.Clear(); + } + + /// + /// Indicates whether the current entity has an identity, eg. Id. + /// + public virtual bool HasIdentity + { + get + { + return _hasIdentity; + } + protected set + { + _hasIdentity = value; + } + } + + public static bool operator ==(Item left, Item right) + { + /*if (ReferenceEquals(null, left)) + return false; + + if(ReferenceEquals(null, right)) + return false;*/ + + return ReferenceEquals(left, right); + + return left.Equals(right); + } + + public static bool operator !=(Item left, Item right) + { + return !(left == right); + } + + /*public virtual bool SameIdentityAs(IEntity other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return SameIdentityAs(other as Entity); + } + + public virtual bool Equals(Entity other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return SameIdentityAs(other); + } + + public virtual Type GetRealType() + { + return GetType(); + } + + public virtual bool SameIdentityAs(Entity other) + { + if (ReferenceEquals(null, other)) + return false; + + if (ReferenceEquals(this, other)) + return true; + + if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity) + return other.Id.Equals(Id); + + return false; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + + return SameIdentityAs(obj as IEntity); + } + + public override int GetHashCode() + { + if (!_hash.HasValue) + _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); + return _hash.Value; + }*/ + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 27a18bd83a..150f3f78f2 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -178,6 +178,7 @@ +