using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; namespace Xamarin.Forms.Core.UnitTests { [TestFixture] public class ListProxyTests : BaseTestFixture { [SetUp] public override void Setup() { base.Setup (); Device.PlatformServices = new MockPlatformServices (); } [TearDown] public override void TearDown() { base.TearDown (); Device.PlatformServices = null; } [Test] public void ListCount() { var list = new List { "foo", "bar" }; var proxy = new ListProxy (list); Assert.AreEqual (list.Count, proxy.Count); list.Add ("baz"); Assert.AreEqual (list.Count, proxy.Count); } [Test] public void CollectionCount() { var list = new Collection { "foo", "bar" }; var proxy = new ListProxy (list); Assert.AreEqual (list.Count, proxy.Count); list.Add ("baz"); Assert.AreEqual (list.Count, proxy.Count); } [Test] [Description ("Count should ensure that the window is created if neccessary")] public void EnumerableInitialCount() { var enumerable = Enumerable.Range (0, 100); var proxy = new ListProxy (enumerable, 10); Assert.AreEqual (10, proxy.Count); } [Test] public void EnumerableCount() { var enumerable = Enumerable.Range (0, 100); var proxy = new ListProxy (enumerable, 10); int changed = 0; proxy.CountChanged += (o, e) => changed++; var enumerator = proxy.GetEnumerator(); enumerator.MoveNext(); Assert.AreEqual (10, proxy.Count); Assert.AreEqual (1, changed); enumerator.MoveNext(); Assert.AreEqual (10, proxy.Count); Assert.AreEqual (1, changed); while (enumerator.MoveNext()) { } enumerator.Dispose(); Assert.AreEqual (100, proxy.Count); Assert.AreEqual (19, changed); using (enumerator = proxy.GetEnumerator()) { Assert.AreEqual (100, proxy.Count); while (enumerator.MoveNext()) Assert.AreEqual (100, proxy.Count); Assert.AreEqual (100, proxy.Count); } Assert.AreEqual (19, changed); } [Test] public void InsideWindowSize() { var numbers = Enumerable.Range (0, 100); var proxy = new ListProxy (numbers, 10); int i = (int)proxy[5]; Assert.That (i, Is.EqualTo (5)); } [Test] public void IndexOutsideWindowSize() { var numbers = Enumerable.Range (0, 100); var proxy = new ListProxy (numbers, 10); int i = (int)proxy[50]; Assert.That (i, Is.EqualTo (50)); } [Test] public void IndexInsideToOutsideWindowSize() { var numbers = Enumerable.Range (0, 100); var proxy = new ListProxy (numbers, 10); int i = (int)proxy[5]; Assert.That (i, Is.EqualTo (5)); i = (int)proxy[50]; Assert.That (i, Is.EqualTo (50)); } [Test] public void IndexOutsideToPreWindowSize() { var numbers = Enumerable.Range (0, 100); var proxy = new ListProxy (numbers, 10); int i = (int)proxy[50]; Assert.That (i, Is.EqualTo (50)); i = (int)proxy[5]; Assert.That (i, Is.EqualTo (5)); } [Test] public void EnumerableIndexOutOfRange() { var numbers = Enumerable.Range (0, 100); var proxy = new ListProxy (numbers); Assert.That (() => proxy[100], Throws.InstanceOf()); } class IntCollection : ICollection { readonly List ints; public IntCollection (IEnumerable ints) { this.ints = ints.ToList(); } public IEnumerator GetEnumerator() { return ints.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void CopyTo (Array array, int index) { throw new NotImplementedException(); } public int Count { get { return ints.Count; }} public object SyncRoot { get { throw new NotImplementedException(); } } public bool IsSynchronized { get { throw new NotImplementedException(); } } public bool IsReadOnly { get { return true; } } } [Test] public void CollectionIndexOutOfRange() { var numbers = new IntCollection (Enumerable.Range (0, 100)); var proxy = new ListProxy (numbers); Assert.That (() => proxy[100], Throws.InstanceOf()); } [Test] public void ListIndexOutOfRange() { var numbers = Enumerable.Range (0, 100).ToList(); var proxy = new ListProxy (numbers); Assert.That (() => proxy[100], Throws.InstanceOf()); } [Test] public void CollectionChangedWhileEnumerating() { var c = new ObservableCollection { "foo", "bar" }; var p = new ListProxy (c); IEnumerator e = p.GetEnumerator(); Assert.IsTrue (e.MoveNext(), "Initial MoveNext() failed, test can't continue"); c.Add ("baz"); Assert.That (() => e.MoveNext(), Throws.InvalidOperationException, "MoveNext did not throw an exception when the underlying collection had changed"); } [Test] public void SynchronizedCollectionAccess() { var collection = new ObservableCollection { "foo" }; var context = new object(); var list = new ListProxy (collection); bool executed = false; BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => { executed = true; Assert.AreSame (collection, enumerable); Assert.AreSame (context, o); Assert.IsNotNull (method); Assert.IsFalse (access); lock (enumerable) method(); }); object value = list[0]; Assert.IsTrue (executed, "Callback was not executed"); } [Test] public void SynchronizedCollectionAdd() { bool invoked = false; Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: action => { invoked = true; action(); }); var collection = new ObservableCollection { "foo" }; var context = new object(); var list = new ListProxy (collection); Assert.IsFalse (invoked, "An invoke shouldn't be executed just setting up ListProxy"); bool executed = false; BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => { executed = true; Assert.AreSame (collection, enumerable); Assert.AreSame (context, o); Assert.IsNotNull (method); Assert.IsFalse (access); lock (enumerable) method(); }); var mre = new ManualResetEvent (false); Task.Factory.StartNew (() => { lock (collection) collection.Add ("foo"); mre.Set(); }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); mre.WaitOne (5000); Assert.IsTrue (executed, "Callback was not executed"); Assert.IsTrue (invoked, "Callback was not executed on the UI thread"); } [Test] public void ClearEnumerable() { var proxy = new ListProxy (Enumerable.Range (0, 100)); var enumerator = proxy.GetEnumerator(); enumerator.MoveNext(); enumerator.MoveNext(); proxy.Clear(); Assert.AreEqual (100, proxy.Count); Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException); } [Test] public void ClearCollection() { var proxy = new ListProxy (new IntCollection (Enumerable.Range (0, 100))); var enumerator = proxy.GetEnumerator(); enumerator.MoveNext(); enumerator.MoveNext(); proxy.Clear(); Assert.AreEqual (100, proxy.Count); Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException); } [Test] public void ClearList() { var proxy = new ListProxy (Enumerable.Range (0, 100).ToList()); var enumerator = proxy.GetEnumerator(); enumerator.MoveNext(); enumerator.MoveNext(); proxy.Clear(); Assert.AreEqual (100, proxy.Count); Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException); } [Test] public void IndexOfValueTypeNonList() { var proxy = new ListProxy (Enumerable.Range (0, 100)); Assert.AreEqual (1, proxy.IndexOf (1)); } [Test] public void EnumeratorForEnumerable() { var proxy = new ListProxy (Enumerable.Range (0, 2)); var enumerator = proxy.GetEnumerator(); Assert.That (enumerator.Current, Is.Null); Assert.That (enumerator.MoveNext(), Is.True); Assert.That (enumerator.Current, Is.EqualTo (0)); Assert.That (enumerator.MoveNext(), Is.True); Assert.That (enumerator.Current, Is.EqualTo (1)); Assert.That (enumerator.MoveNext(), Is.False); } [Test] public void ProxyIsWeaklyHeldByINotifyCollectionChanged() { ObservableCollection collection = new ObservableCollection(); WeakReference weakProxy = null; int i = 0; Action create = null; create = () => { if (i++ < 1024) { create(); return; } var proxy = new ListProxy (collection); weakProxy = new WeakReference (proxy); }; create(); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Assert.That (weakProxy.IsAlive, Is.False); } [Test] public void IEnumerableAddDoesNotReport0() { var custom = new CustomINCC(); custom.Add ("test"); custom.Add ("test2"); var proxy = new ListProxy (custom); Assert.That (proxy.Count, Is.EqualTo (2)); custom.Add ("testing"); Assert.That (proxy.Count, Is.EqualTo (3)); } class CustomINCC : IEnumerable, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; List Items = new List (); public void Add (string s) { Items.Add(s); if (CollectionChanged != null) CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, s)); } public IEnumerator GetEnumerator () { return Items.GetEnumerator (); } IEnumerator IEnumerable.GetEnumerator () { return Items.GetEnumerator (); } } } }