// Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Copyright (c) 2007 Novell, Inc. (http://www.novell.com) // Copyright 2011 Xamarin Inc. // // Authors: // Chris Toshok (toshok@ximian.com) // Brian O'Keefe (zer0keefie@gmail.com) // Marek Safar (marek.safar@gmail.com) // using System.Collections.ObjectModel; using System.Collections.Specialized; using NUnit.Framework; using System.ComponentModel; using System.Collections.Generic; using System; using System.Collections; using MonoTests.System.Collections.Specialized; namespace MonoTests.System.Collections.ObjectModel { [TestFixture] public class ObservableCollectionTest { [Test] public void Constructor () { var list = new List { 3 }; var col = new ObservableCollection (list); col.Add (5); Assert.AreEqual (1, list.Count, "#1"); col = new ObservableCollection ((IEnumerable) list); col.Add (5); Assert.AreEqual (1, list.Count, "#2"); } [Test] public void Constructor_Invalid () { try { new ObservableCollection ((List) null); Assert.Fail ("#1"); } catch (ArgumentNullException) { } try { new ObservableCollection ((IEnumerable) null); Assert.Fail ("#2"); } catch (ArgumentNullException) { } } [Test] public void Insert() { bool reached = false; ObservableCollection col = new ObservableCollection (); col.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { reached = true; Assert.AreEqual (NotifyCollectionChangedAction.Add, e.Action, "INS_1"); Assert.AreEqual (0, e.NewStartingIndex, "INS_2"); Assert.AreEqual (-1, e.OldStartingIndex, "INS_3"); Assert.AreEqual (1, e.NewItems.Count, "INS_4"); Assert.AreEqual (5, (int)e.NewItems [0], "INS_5"); Assert.AreEqual (null, e.OldItems, "INS_6"); }; col.Insert (0, 5); Assert.IsTrue (reached, "INS_5"); } [Test] public void RemoveAt() { bool reached = false; ObservableCollection col = new ObservableCollection (); col.Insert (0, 5); col.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { reached = true; Assert.AreEqual (NotifyCollectionChangedAction.Remove, e.Action, "REMAT_1"); Assert.AreEqual (-1, e.NewStartingIndex, "REMAT_2"); Assert.AreEqual (0, e.OldStartingIndex, "REMAT_3"); Assert.AreEqual (null, e.NewItems, "REMAT_4"); Assert.AreEqual (1, e.OldItems.Count, "REMAT_5"); Assert.AreEqual (5, (int)e.OldItems [0], "REMAT_6"); }; col.RemoveAt (0); Assert.IsTrue (reached, "REMAT_7"); } [Test] public void Move() { bool reached = false; ObservableCollection col = new ObservableCollection (); col.Insert (0, 0); col.Insert (1, 1); col.Insert (2, 2); col.Insert (3, 3); col.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { reached = true; Assert.AreEqual (NotifyCollectionChangedAction.Move, e.Action, "MOVE_1"); Assert.AreEqual (3, e.NewStartingIndex, "MOVE_2"); Assert.AreEqual (1, e.OldStartingIndex, "MOVE_3"); Assert.AreEqual (1, e.NewItems.Count, "MOVE_4"); Assert.AreEqual (1, e.NewItems [0], "MOVE_5"); Assert.AreEqual (1, e.OldItems.Count, "MOVE_6"); Assert.AreEqual (1, e.OldItems [0], "MOVE_7"); }; col.Move (1, 3); Assert.IsTrue (reached, "MOVE_8"); } [Test] public void Add() { ObservableCollection collection = new ObservableCollection (); bool propertyChanged = false; List changedProps = new List (); NotifyCollectionChangedEventArgs args = null; ((INotifyPropertyChanged)collection).PropertyChanged += delegate (object sender, PropertyChangedEventArgs e) { propertyChanged = true; changedProps.Add (e.PropertyName); }; collection.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { args = e; }; collection.Add ('A'); Assert.IsTrue (propertyChanged, "ADD_1"); Assert.IsTrue (changedProps.Contains ("Count"), "ADD_2"); Assert.IsTrue (changedProps.Contains ("Item[]"), "ADD_3"); CollectionChangedEventValidators.ValidateAddOperation (args, new char [] { 'A' }, 0, "ADD_4"); } [Test] public void Remove() { ObservableCollection collection = new ObservableCollection (); bool propertyChanged = false; List changedProps = new List (); NotifyCollectionChangedEventArgs args = null; collection.Add ('A'); collection.Add ('B'); collection.Add ('C'); ((INotifyPropertyChanged)collection).PropertyChanged += delegate (object sender, PropertyChangedEventArgs e) { propertyChanged = true; changedProps.Add (e.PropertyName); }; collection.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { args = e; }; collection.Remove ('B'); Assert.IsTrue (propertyChanged, "REM_1"); Assert.IsTrue (changedProps.Contains ("Count"), "REM_2"); Assert.IsTrue (changedProps.Contains ("Item[]"), "REM_3"); CollectionChangedEventValidators.ValidateRemoveOperation (args, new char [] { 'B' }, 1, "REM_4"); } [Test] public void Set() { ObservableCollection collection = new ObservableCollection (); bool propertyChanged = false; List changedProps = new List (); NotifyCollectionChangedEventArgs args = null; collection.Add ('A'); collection.Add ('B'); collection.Add ('C'); ((INotifyPropertyChanged)collection).PropertyChanged += delegate (object sender, PropertyChangedEventArgs e) { propertyChanged = true; changedProps.Add (e.PropertyName); }; collection.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { args = e; }; collection [2] = 'I'; Assert.IsTrue (propertyChanged, "SET_1"); Assert.IsTrue (changedProps.Contains ("Item[]"), "SET_2"); CollectionChangedEventValidators.ValidateReplaceOperation (args, new char [] { 'C' }, new char [] { 'I' }, 2, "SET_3"); } [Test] public void Reentrant() { ObservableCollection collection = new ObservableCollection (); bool propertyChanged = false; List changedProps = new List (); NotifyCollectionChangedEventArgs args = null; collection.Add ('A'); collection.Add ('B'); collection.Add ('C'); PropertyChangedEventHandler pceh = delegate (object sender, PropertyChangedEventArgs e) { propertyChanged = true; changedProps.Add (e.PropertyName); }; // Adding a PropertyChanged event handler ((INotifyPropertyChanged)collection).PropertyChanged += pceh; collection.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { args = e; }; collection.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { // This one will attempt to break reentrancy try { collection.Add ('X'); Assert.Fail ("Reentrancy should not be allowed."); } catch (InvalidOperationException) { } }; collection [2] = 'I'; Assert.IsTrue (propertyChanged, "REENT_1"); Assert.IsTrue (changedProps.Contains ("Item[]"), "REENT_2"); CollectionChangedEventValidators.ValidateReplaceOperation (args, new char [] { 'C' }, new char [] { 'I' }, 2, "REENT_3"); // Removing the PropertyChanged event handler should work as well: ((INotifyPropertyChanged)collection).PropertyChanged -= pceh; } //Private test class for protected members of ObservableCollection private class ObservableCollectionTestHelper : ObservableCollection { internal void DoubleEnterReentrant() { IDisposable object1 = BlockReentrancy (); IDisposable object2 = BlockReentrancy (); Assert.AreSame (object1, object2); //With double block, try the reentrant: NotifyCollectionChangedEventArgs args = null; CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { args = e; }; // We need a second callback for reentrancy to matter CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { // Doesn't need to do anything; just needs more than one callback registered. }; // Try adding - this should cause reentrancy, and fail try { Add ('I'); Assert.Fail ("Reentrancy should not be allowed. -- #2"); } catch (InvalidOperationException) { } // Release the first reentrant object1.Dispose (); // Try adding again - this should cause reentrancy, and fail again try { Add ('J'); Assert.Fail ("Reentrancy should not be allowed. -- #3"); } catch (InvalidOperationException) { } // Release the reentrant a second time object1.Dispose (); // This last add should work fine. Add ('K'); CollectionChangedEventValidators.ValidateAddOperation (args, new char [] { 'K' }, 0, "REENTHELP_1"); } } [Test] public void ReentrantReuseObject() { ObservableCollectionTestHelper helper = new ObservableCollectionTestHelper (); helper.DoubleEnterReentrant (); } [Test] public void Clear() { List initial = new List (); initial.Add ('A'); initial.Add ('B'); initial.Add ('C'); ObservableCollection collection = new ObservableCollection (initial); bool propertyChanged = false; List changedProps = new List (); NotifyCollectionChangedEventArgs args = null; ((INotifyPropertyChanged)collection).PropertyChanged += delegate (object sender, PropertyChangedEventArgs e) { propertyChanged = true; changedProps.Add (e.PropertyName); }; collection.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e) { args = e; }; collection.Clear (); Assert.IsTrue (propertyChanged, "CLEAR_1"); Assert.IsTrue (changedProps.Contains ("Count"), "CLEAR_2"); Assert.IsTrue (changedProps.Contains ("Item[]"), "CLEAR_3"); CollectionChangedEventValidators.ValidateResetOperation (args, "CLEAR_4"); } } }