From 76eddc2e1341d01fe2e24dce6775336b522edc75 Mon Sep 17 00:00:00 2001 From: Hugh Bellamy Date: Tue, 12 Mar 2019 16:11:42 +0000 Subject: [PATCH 1/2] Cleanup BindingConext --- .../System/Windows/Forms/BindingContext.cs | 449 +++++++++--------- 1 file changed, 217 insertions(+), 232 deletions(-) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs index 48541d1375a..5d8cd135398 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs @@ -2,438 +2,423 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace System.Windows.Forms { +using System.Collections; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; - using System; - using Microsoft.Win32; - using System.ComponentModel; - using System.Collections; - using System.Globalization; - using System.Diagnostics.CodeAnalysis; - - /// +namespace System.Windows.Forms +{ /// - /// Manages the collection of System.Windows.Forms.BindingManagerBase - /// objects for a Win Form. + /// Manages the collection of System.Windows.Forms.BindingManagerBase + /// objects for a Win Form. /// [DefaultEvent(nameof(CollectionChanged))] - public class BindingContext : ICollection { + public class BindingContext : ICollection + { + private Hashtable _listManagers; - private Hashtable listManagers; + /// + /// Initializes a new instance of the System.Windows.Forms.BindingContext class. + /// + public BindingContext() + { + _listManagers = new Hashtable(); + } - /// - /// /// - /// - /// Gets the total number of System.Windows.Forms.BindingManagerBases - /// objects. - /// + /// Gets the total number of System.Windows.Forms.BindingManagerBases objects. /// - int ICollection.Count { - get { + int ICollection.Count + { + get + { ScrubWeakRefs(); - return listManagers.Count; + return _listManagers.Count; } } - /// - /// /// - /// /// Copies the elements of the collection into a specified array, starting /// at the collection index. - /// /// void ICollection.CopyTo(Array ar, int index) { ScrubWeakRefs(); - listManagers.CopyTo(ar, index); + _listManagers.CopyTo(ar, index); } - /// - /// /// - /// /// Gets an enumerator for the collection. - /// /// IEnumerator IEnumerable.GetEnumerator() { ScrubWeakRefs(); - return listManagers.GetEnumerator(); + return _listManagers.GetEnumerator(); } - /// - /// /// - /// - /// Gets a value indicating whether the collection is read-only. - /// + /// Gets a value indicating whether the collection is read-only. /// - public bool IsReadOnly { - get { - return false; - } - } + public bool IsReadOnly => false; - /// - /// /// - /// /// Gets a value indicating whether the collection is synchronized. - /// /// - bool ICollection.IsSynchronized { - get { - // so the user will know that it has to lock this object - return false; - } - } + bool ICollection.IsSynchronized => false; - /// - /// /// - /// Gets an object to use for synchronization (thread safety). + /// Gets an object to use for synchronization (thread safety). /// - object ICollection.SyncRoot { - get { - return null; - } - } - + object ICollection.SyncRoot => null; - /// /// - /// Initializes a new instance of the System.Windows.Forms.BindingContext class. + /// Gets the System.Windows.Forms.BindingManagerBase associated with the specified + /// data source. /// - public BindingContext() { - listManagers = new Hashtable(); - } + public BindingManagerBase this[object dataSource] => this[dataSource, string.Empty]; - /// /// - /// - /// Gets the System.Windows.Forms.BindingManagerBase - /// associated with the specified data source. - /// + /// Gets the System.Windows.Forms.BindingManagerBase associated with the specified + /// data source and data member. /// - public BindingManagerBase this[object dataSource] { - get { - return this[dataSource, ""]; - } - } - - /// - /// - /// Gets the System.Windows.Forms.BindingManagerBase associated with the specified data source and - /// data member. - /// - public BindingManagerBase this[object dataSource, string dataMember] { - get { - return EnsureListManager(dataSource, dataMember); - } + public BindingManagerBase this[object dataSource, string dataMember] + { + get => EnsureListManager(dataSource, dataMember); } - /// /// - /// Adds the listManager to the collection. An ArgumentNullException is thrown if this listManager - /// is null. An exception is thrown if a listManager to the same target and Property as an existing listManager or - /// if the listManager's column isn't a valid column given this DataSource.Table's schema. + /// Adds the listManager to the collection. An ArgumentNullException is thrown if this + /// listManager is null. An exception is thrown if a listManager to the same target + /// and Property as an existing listManager or if the listManager's column isn't a + /// valid column given this DataSource.Table's schema. /// Fires the CollectionChangedEvent. /// - internal protected void Add(object dataSource, BindingManagerBase listManager) { - /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */ + /// + /// This method is obsolete and unused. + /// + internal protected void Add(object dataSource, BindingManagerBase listManager) + { AddCore(dataSource, listManager); OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, dataSource)); } - /// - /// - /// - protected virtual void AddCore(object dataSource, BindingManagerBase listManager) { - /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */ + /// + /// This method is obsolete and unused. + /// + protected virtual void AddCore(object dataSource, BindingManagerBase listManager) + { if (dataSource == null) + { throw new ArgumentNullException(nameof(dataSource)); + } if (listManager == null) + { throw new ArgumentNullException(nameof(listManager)); + } - // listManagers[dataSource] = listManager; - listManagers[GetKey(dataSource, "")] = new WeakReference(listManager, false); + _listManagers[GetKey(dataSource, string.Empty)] = new WeakReference(listManager, false); } - - /// /// - /// - /// Occurs when the collection has changed. - /// + /// Occurs when the collection has changed. /// + /// + /// This method is obsolete and unused. + /// [SRDescription(nameof(SR.collectionChangedEventDescr)), EditorBrowsable(EditorBrowsableState.Never), Browsable(false)] - public event CollectionChangeEventHandler CollectionChanged { - /* !!THIS EVENT IS OBSOLETE AND UNUSED!! */ + public event CollectionChangeEventHandler CollectionChanged + { [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")] - add { + add + { throw new NotImplementedException(); } [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")] - remove { + remove + { } } - /// /// /// Clears the collection of any bindings. /// Fires the CollectionChangedEvent. /// - internal protected void Clear() { - /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */ + /// + /// This method is obsolete and unused. + /// + internal protected void Clear() + { ClearCore(); OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null)); } - /// /// - /// - /// Clears the collection. - /// + /// Clears the collection. /// - protected virtual void ClearCore() { - /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */ - listManagers.Clear(); - } + /// + /// This method is obsolete and unused. + /// + protected virtual void ClearCore() => _listManagers.Clear(); - /// /// - /// Gets a value indicating whether the System.Windows.Forms.BindingContext - /// contains the specified - /// data source. + /// Gets a value indicating whether the System.Windows.Forms.BindingContext contains + /// the specified data source. /// - public bool Contains(object dataSource) { - return Contains(dataSource, ""); - } + public bool Contains(object dataSource) => Contains(dataSource, string.Empty); - /// /// - /// Gets a value indicating whether the System.Windows.Forms.BindingContext - /// contains the specified data source and data member. + /// Gets a value indicating whether the System.Windows.Forms.BindingContext + /// contains the specified data source and data member. /// - public bool Contains(object dataSource, string dataMember) { - return listManagers.ContainsKey(GetKey(dataSource, dataMember)); + public bool Contains(object dataSource, string dataMember) + { + return _listManagers.ContainsKey(GetKey(dataSource, dataMember)); } - internal HashKey GetKey(object dataSource, string dataMember) { + internal HashKey GetKey(object dataSource, string dataMember) + { return new HashKey(dataSource, dataMember); } - /// - /// - /// - /// - // - internal class HashKey { - WeakReference wRef; - int dataSourceHashCode; - string dataMember; + internal class HashKey + { + private WeakReference _wRef; + private int _dataSourceHashCode; + private string _dataMember; - internal HashKey(object dataSource, string dataMember) { + internal HashKey(object dataSource, string dataMember) + { if (dataSource == null) + { throw new ArgumentNullException(nameof(dataSource)); + } if (dataMember == null) - dataMember = ""; - // The dataMember should be case insensitive. - // so convert the dataMember to lower case - // - this.wRef = new WeakReference(dataSource, false); - this.dataSourceHashCode = dataSource.GetHashCode(); - this.dataMember = dataMember.ToLower(CultureInfo.InvariantCulture); - } + { + dataMember = string.Empty; + } - /// - /// - /// - /// - public override int GetHashCode() { - return dataSourceHashCode * dataMember.GetHashCode(); + // The dataMember should be case insensitive, so convert the + // dataMember to lower case + _wRef = new WeakReference(dataSource, false); + _dataSourceHashCode = dataSource.GetHashCode(); + _dataMember = dataMember.ToLower(CultureInfo.InvariantCulture); } - /// - /// - /// - /// - public override bool Equals(object target) { - if (target is HashKey) { - HashKey keyTarget = (HashKey)target; - return wRef.Target == keyTarget.wRef.Target && dataMember == keyTarget.dataMember; + public override int GetHashCode() => _dataSourceHashCode * _dataMember.GetHashCode(); + + public override bool Equals(object target) + { + if (!(target is HashKey keyTarget)) + { + return false; } - return false; + + return _wRef.Target == keyTarget._wRef.Target && _dataMember == keyTarget._dataMember; } } - /// - /// /// - /// This method is called whenever the collection changes. Overriders - /// of this method should call the base implementation of this method. - /// NOTE: This shipped in Everett, so we need to keep it, but we don't do - /// anything here. + /// This method is called whenever the collection changes. Overriders of this method + /// should call the base implementation of this method. /// - protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent) { + protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent) + { } - /// /// /// Removes the given listManager from the collection. - /// An ArgumentNullException is thrown if this listManager is null. An ArgumentException is thrown - /// if this listManager doesn't belong to this collection. + /// An ArgumentNullException is thrown if this listManager is null. An ArgumentException + /// is thrown if this listManager doesn't belong to this collection. /// The CollectionChanged event is fired if it succeeds. /// - internal protected void Remove(object dataSource) { - /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */ + /// + /// This method is obsolete and unused. + /// + internal protected void Remove(object dataSource) + { RemoveCore(dataSource); OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, dataSource)); } - /// - /// - /// - /// - protected virtual void RemoveCore(object dataSource) { - /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */ - listManagers.Remove(GetKey(dataSource, "")); + /// + /// This method is obsolete and unused. + /// + protected virtual void RemoveCore(object dataSource) + { + _listManagers.Remove(GetKey(dataSource, string.Empty)); } - /// /// - /// Create a suitable binding manager for the specified dataSource/dataMember combination. - /// - If one has already been created and cached by this BindingContext, return that instead. - /// - If the data source is an ICurrencyManagerProvider, just delegate to the data source. + /// Create a suitable binding manager for the specified dataSource/dataMember combination. + /// - If one has already been created and cached by this BindingContext, return that + /// instead. + /// - If the data source is an ICurrencyManagerProvider, just delegate to the data + /// source. /// - internal BindingManagerBase EnsureListManager(object dataSource, string dataMember) { + internal BindingManagerBase EnsureListManager(object dataSource, string dataMember) + { BindingManagerBase bindingManagerBase = null; if (dataMember == null) - dataMember = ""; + { + dataMember = string.Empty; + } // Check whether data source wants to provide its own binding managers // (but fall through to old logic if it fails to provide us with one) - // - if (dataSource is ICurrencyManagerProvider) { - bindingManagerBase = (dataSource as ICurrencyManagerProvider).GetRelatedCurrencyManager(dataMember); - - if (bindingManagerBase != null) { + if (dataSource is ICurrencyManagerProvider currencyManagerProvider) + { + bindingManagerBase = currencyManagerProvider.GetRelatedCurrencyManager(dataMember); + if (bindingManagerBase != null) + { return bindingManagerBase; } } // Check for previously created binding manager - // HashKey key = GetKey(dataSource, dataMember); - WeakReference wRef; - wRef = listManagers[key] as WeakReference; + WeakReference wRef = _listManagers[key] as WeakReference; if (wRef != null) - bindingManagerBase = (BindingManagerBase) wRef.Target; - if (bindingManagerBase != null) { + { + bindingManagerBase = (BindingManagerBase)wRef.Target; + } + if (bindingManagerBase != null) + { return bindingManagerBase; } - if (dataMember.Length == 0) { + if (dataMember.Length == 0) + { // No data member specified, so create binding manager directly on the data source - // - if (dataSource is IList || dataSource is IListSource) { + if (dataSource is IList || dataSource is IListSource) + { // IListSource so we can bind the dataGrid to a table and a dataSet bindingManagerBase = new CurrencyManager(dataSource); } - else { + else + { // Otherwise assume simple property binding bindingManagerBase = new PropertyManager(dataSource); } } - else { + else + { // Data member specified, so get data source's binding manager, and hook a 'related' binding manager to it - // - int lastDot = dataMember.LastIndexOf("."); - string dataPath = (lastDot == -1) ? "" : dataMember.Substring(0, lastDot); + int lastDot = dataMember.LastIndexOf("."); + string dataPath = (lastDot == -1) ? string.Empty : dataMember.Substring(0, lastDot); string dataField = dataMember.Substring(lastDot + 1); BindingManagerBase formerManager = EnsureListManager(dataSource, dataPath); PropertyDescriptor prop = formerManager.GetItemProperties().Find(dataField, true); if (prop == null) + { throw new ArgumentException(string.Format(SR.RelatedListManagerChild, dataField)); + } if (typeof(IList).IsAssignableFrom(prop.PropertyType)) + { bindingManagerBase = new RelatedCurrencyManager(formerManager, dataField); + } else + { bindingManagerBase = new RelatedPropertyManager(formerManager, dataField); + } } // if wRef == null, then it is the first time we want this bindingManagerBase: so add it // if wRef != null, then the bindingManagerBase was GC'ed at some point: keep the old wRef and change its target if (wRef == null) - listManagers.Add(key, new WeakReference(bindingManagerBase, false)); + { + _listManagers.Add(key, new WeakReference(bindingManagerBase, false)); + } else + { wRef.Target = bindingManagerBase; + } ScrubWeakRefs(); // Return the final binding manager return bindingManagerBase; } - // may throw - private static void CheckPropertyBindingCycles(BindingContext newBindingContext, Binding propBinding) { + private static void CheckPropertyBindingCycles(BindingContext newBindingContext, Binding propBinding) + { if (newBindingContext == null || propBinding == null) + { return; - if (newBindingContext.Contains(propBinding.BindableComponent, "")) { + } + + if (newBindingContext.Contains(propBinding.BindableComponent, string.Empty)) + { // this way we do not add a bindingManagerBase to the // bindingContext if there isn't one already - BindingManagerBase bindingManagerBase = newBindingContext.EnsureListManager(propBinding.BindableComponent, ""); - for (int i = 0; i < bindingManagerBase.Bindings.Count; i++) { + BindingManagerBase bindingManagerBase = newBindingContext.EnsureListManager(propBinding.BindableComponent, string.Empty); + for (int i = 0; i < bindingManagerBase.Bindings.Count; i++) + { Binding binding = bindingManagerBase.Bindings[i]; - if (binding.DataSource == propBinding.BindableComponent) { + if (binding.DataSource == propBinding.BindableComponent) + { if (propBinding.BindToObject.BindingMemberInfo.BindingMember.Equals(binding.PropertyName)) - throw new ArgumentException(string.Format(SR.DataBindingCycle, binding.PropertyName), "propBinding"); - } else if (propBinding.BindToObject.BindingManagerBase is PropertyManager) + { + throw new ArgumentException(string.Format(SR.DataBindingCycle, binding.PropertyName), nameof(propBinding)); + } + } + else if (propBinding.BindToObject.BindingManagerBase is PropertyManager) + { CheckPropertyBindingCycles(newBindingContext, binding); + } } } } - private void ScrubWeakRefs() { + private void ScrubWeakRefs() + { ArrayList cleanupList = null; - foreach (DictionaryEntry de in listManagers) { - WeakReference wRef = (WeakReference) de.Value; - if (wRef.Target == null) { - if (cleanupList == null) { + foreach (DictionaryEntry de in _listManagers) + { + WeakReference wRef = (WeakReference)de.Value; + if (wRef.Target == null) + { + if (cleanupList == null) + { cleanupList = new ArrayList(); } cleanupList.Add(de.Key); } } - if (cleanupList != null) { - foreach (object o in cleanupList) { - listManagers.Remove(o); + if (cleanupList != null) + { + foreach (object o in cleanupList) + { + _listManagers.Remove(o); } } } - /// /// - /// Associates a Binding with a different BindingContext. Intended for use by components that support - /// IBindableComponent, to update their Bindings when the value of IBindableComponent.BindingContext - /// is changed. + /// Associates a Binding with a different BindingContext. Intended for use by components + /// that support IBindableComponent, to update their Bindings when the value of + /// IBindableComponent.BindingContext is changed. /// - public static void UpdateBinding(BindingContext newBindingContext, Binding binding) { + public static void UpdateBinding(BindingContext newBindingContext, Binding binding) + { BindingManagerBase oldManager = binding.BindingManagerBase; - if (oldManager != null) { + if (oldManager != null) + { oldManager.Bindings.Remove(binding); } - if (newBindingContext != null) { + if (newBindingContext != null) + { // we need to first check for cycles before adding this binding to the collection // of bindings. if (binding.BindToObject.BindingManagerBase is PropertyManager) + { CheckPropertyBindingCycles(newBindingContext, binding); + } BindToObject bindTo = binding.BindToObject; BindingManagerBase newManager = newBindingContext.EnsureListManager(bindTo.DataSource, bindTo.BindingMemberInfo.BindingPath); From 5b3e6671976aff42e30d7703213c1a7ff3968745 Mon Sep 17 00:00:00 2001 From: Hugh Bellamy Date: Sat, 30 Mar 2019 13:01:17 +0000 Subject: [PATCH 2/2] Add tests and fix validation --- .../System/Windows/Forms/BindingContext.cs | 5 + .../Windows/Forms/BindingContextTests.cs | 1025 +++++++++++++++++ 2 files changed, 1030 insertions(+) create mode 100644 src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BindingContextTests.cs diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs index 5d8cd135398..e4eb75dbfd8 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BindingContext.cs @@ -405,6 +405,11 @@ private void ScrubWeakRefs() /// public static void UpdateBinding(BindingContext newBindingContext, Binding binding) { + if (binding == null) + { + throw new ArgumentNullException(nameof(binding)); + } + BindingManagerBase oldManager = binding.BindingManagerBase; if (oldManager != null) { diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BindingContextTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BindingContextTests.cs new file mode 100644 index 00000000000..2d9273201bc --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BindingContextTests.cs @@ -0,0 +1,1025 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using Moq; +using Xunit; + +namespace System.Windows.Forms.Tests +{ + public class BindingContextTests + { + [Fact] + public void BindingContext_Ctor_Default() + { + var context = new BindingContext(); + Assert.False(context.IsReadOnly); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_ICollection_GetProperties_ReturnsExpected() + { + ICollection context = new BindingContext(); + Assert.False(context.IsSynchronized); + Assert.Null(context.SyncRoot); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_Count_GetWithNullWeakReferenceTarget_ScrubsWeakRefs() + { + var context = new BindingContext(); + var dataSource1 = new DataSource { Property = 1 }; + var dataSource2 = new DataSource { Property = 2 }; + PropertyManager manager1 = Assert.IsAssignableFrom(context[dataSource1, "Property"]); + PropertyManager manager2 = Assert.IsAssignableFrom(context[dataSource2, "Property"]); + var array = new DictionaryEntry[4]; + ((ICollection)context).CopyTo(array, 0); + + WeakReference reference1 = Assert.IsType(array.Single(p => ((WeakReference)p.Value).Target == manager1).Value); + WeakReference reference2 = Assert.IsType(array.Single(p => ((WeakReference)p.Value).Target == manager2).Value); + Assert.Same(manager1, reference1.Target); + Assert.Same(manager2, reference2.Target); + + // Simulate a GC by assigning the weak reference to null. + reference1.Target = null; + reference2.Target = null; + + // Verify these weak references have been scrubbed. + Assert.Equal(2, ((ICollection)context).Count); + } + + [Fact] + public void BindingContext_Add_NullDataSource_ThrowsArgumentNullException() + { + var context = new BindingContext(); + var source = new BindingSource(); + Assert.Throws("dataSource", () => context.Add(null, source.CurrencyManager)); + } + + [Fact] + public void BindingContext_Add_Invoke_GetReturnsExpected() + { + var context = new BindingContext(); + var source1 = new BindingSource(); + var source2 = new BindingSource(); + var dataSource = new DataSource(); + context.Add(dataSource, source1.CurrencyManager); + Assert.Single(context); + + Assert.Same(source1.CurrencyManager, context[dataSource]); + Assert.Same(source1.CurrencyManager, context[dataSource, null]); + Assert.Same(source1.CurrencyManager, context[dataSource, string.Empty]); + + // Set new value. + context.Add(dataSource, source2.CurrencyManager); + Assert.Single(context); + Assert.Same(source2.CurrencyManager, context[dataSource]); + Assert.Same(source2.CurrencyManager, context[dataSource, null]); + Assert.Same(source2.CurrencyManager, context[dataSource, string.Empty]); + } + + [Fact] + public void BindingContext_Add_InvokeMultiple_Success() + { + var context = new BindingContext(); + var source1 = new BindingSource(); + var source2 = new BindingSource(); + var dataSource1 = new DataSource(); + var dataSource2 = new DataSource(); + context.Add(dataSource1, source1.CurrencyManager); + context.Add(dataSource2, source2.CurrencyManager); + Assert.Equal(2, ((ICollection)context).Count); + + Assert.Same(source1.CurrencyManager, context[dataSource1]); + Assert.Same(source2.CurrencyManager, context[dataSource2]); + } + + [Fact] + public void BindingContext_Add_NullListManager_ThrowsArgumentNullException() + { + var context = new BindingContext(); + Assert.Throws("listManager", () => context.Add(1, null)); + } + + [Fact] + public void BindingContext_AddCore_Invoke_GetReturnsExpected() + { + var context = new SubBindingContext(); + var source1 = new BindingSource(); + var source2 = new BindingSource(); + var dataSource = new DataSource(); + context.AddCore(dataSource, source1.CurrencyManager); + Assert.Single(context); + + Assert.Same(source1.CurrencyManager, context[dataSource]); + Assert.Same(source1.CurrencyManager, context[dataSource, null]); + Assert.Same(source1.CurrencyManager, context[dataSource, string.Empty]); + + // Set new value. + context.AddCore(dataSource, source2.CurrencyManager); + Assert.Single(context); + Assert.Same(source2.CurrencyManager, context[dataSource]); + Assert.Same(source2.CurrencyManager, context[dataSource, null]); + Assert.Same(source2.CurrencyManager, context[dataSource, string.Empty]); + } + + [Fact] + public void BindingContext_AddCore_InvokeMultiple_Success() + { + var context = new SubBindingContext(); + var source1 = new BindingSource(); + var source2 = new BindingSource(); + var dataSource1 = new DataSource(); + var dataSource2 = new DataSource(); + context.AddCore(dataSource1, source1.CurrencyManager); + context.AddCore(dataSource2, source2.CurrencyManager); + Assert.Equal(2, ((ICollection)context).Count); + + Assert.Same(source1.CurrencyManager, context[dataSource1]); + Assert.Same(source2.CurrencyManager, context[dataSource2]); + } + + [Fact] + public void BindingContext_AddCore_NullDataSource_ThrowsArgumentNullException() + { + var context = new SubBindingContext(); + var source = new BindingSource(); + Assert.Throws("dataSource", () => context.AddCore(null, source.CurrencyManager)); + } + + [Fact] + public void BindingContext_AddCore_NullListManager_ThrowsArgumentNullException() + { + var context = new SubBindingContext(); + Assert.Throws("listManager", () => context.AddCore(1, null)); + } + + [Fact] + public void BindingContext_CopyTo_Invoke_Success() + { + var context = new BindingContext(); + var source = new BindingSource(); + var dataSource = new DataSource(); + context.Add(dataSource, source.CurrencyManager); + + var array = new object[] { 1, 2, 3 }; + ((ICollection)context).CopyTo(array, 1); + Assert.Equal(1, array[0]); + Assert.NotNull(Assert.IsType(array[1]).Key); + Assert.Equal(source.CurrencyManager, Assert.IsType(Assert.IsType(array[1]).Value).Target); + Assert.Equal(3, array[2]); + } + + [Fact] + public void BindingContext_CopyTo_WithNullWeakReferenceTarget_ScrubsWeakRefs() + { + var context = new BindingContext(); + var dataSource1 = new DataSource { Property = 1 }; + var dataSource2 = new DataSource { Property = 2 }; + PropertyManager manager1 = Assert.IsAssignableFrom(context[dataSource1, "Property"]); + PropertyManager manager2 = Assert.IsAssignableFrom(context[dataSource2, "Property"]); + var array = new DictionaryEntry[4]; + ((ICollection)context).CopyTo(array, 0); + + WeakReference reference1 = Assert.IsType(array.Single(p => ((WeakReference)p.Value).Target == manager1).Value); + WeakReference reference2 = Assert.IsType(array.Single(p => ((WeakReference)p.Value).Target == manager2).Value); + Assert.Same(manager1, reference1.Target); + Assert.Same(manager2, reference2.Target); + + // Simulate a GC by assigning the weak reference to null. + reference1.Target = null; + reference2.Target = null; + + // Verify these weak references have been scrubbed. + var destArray = new object[] { 1, 2, 3, 4 }; + ((ICollection)context).CopyTo(destArray, 1); + Assert.Equal(1, destArray[0]); + Assert.IsType(destArray[1]); + Assert.IsType(destArray[2]); + Assert.Equal(4, destArray[3]); + } + + [Fact] + public void BindingContext_GetEnumerator_WithNullWeakReferenceTarget_ScrubsWeakRefs() + { + var context = new BindingContext(); + var dataSource1 = new DataSource { Property = 1 }; + var dataSource2 = new DataSource { Property = 2 }; + PropertyManager manager1 = Assert.IsAssignableFrom(context[dataSource1, "Property"]); + PropertyManager manager2 = Assert.IsAssignableFrom(context[dataSource2, "Property"]); + var array = new DictionaryEntry[4]; + ((ICollection)context).CopyTo(array, 0); + + WeakReference reference1 = Assert.IsType(array.Single(p => ((WeakReference)p.Value).Target == manager1).Value); + WeakReference reference2 = Assert.IsType(array.Single(p => ((WeakReference)p.Value).Target == manager2).Value); + Assert.Same(manager1, reference1.Target); + Assert.Same(manager2, reference2.Target); + + // Simulate a GC by assigning the weak reference to null. + reference1.Target = null; + reference2.Target = null; + + // Verify these weak references have been scrubbed. + IEnumerator enumerator = ((ICollection)context).GetEnumerator(); + Assert.True(enumerator.MoveNext()); + Assert.IsType(enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.IsType(enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + + [Fact] + public void BindingContext_Remove_Invoke_Success() + { + var context = new BindingContext(); + var source1 = new BindingSource(); + var dataSource1 = new DataSource(); + var source2 = new BindingSource(); + var dataSource2 = new DataSource(); + context.Add(dataSource1, source1.CurrencyManager); + context.Add(dataSource2, source2.CurrencyManager); + + context.Remove(dataSource1); + Assert.Single(context); + + // Remove again. + context.Remove(dataSource1); + Assert.Single(context); + + context.Remove(dataSource2); + Assert.Empty(context); + + // Remove again. + context.Remove(dataSource2); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_Remove_NullDataSource_ThrowsArgumentNullException() + { + var context = new SubBindingContext(); + Assert.Throws("dataSource", () => context.Remove(null)); + } + + [Fact] + public void BindingContext_RemoveCore_Invoke_Success() + { + var context = new SubBindingContext(); + var source1 = new BindingSource(); + var dataSource1 = new DataSource(); + var source2 = new BindingSource(); + var dataSource2 = new DataSource(); + context.Add(dataSource1, source1.CurrencyManager); + context.Add(dataSource2, source2.CurrencyManager); + + context.RemoveCore(dataSource1); + Assert.Single(context); + + // Remove again. + context.RemoveCore(dataSource1); + Assert.Single(context); + + context.RemoveCore(dataSource2); + Assert.Empty(context); + + // Remove again. + context.RemoveCore(dataSource2); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_RemoveCore_NullDataSource_ThrowsArgumentNullException() + { + var context = new SubBindingContext(); + Assert.Throws("dataSource", () => context.RemoveCore(null)); + } + + [Fact] + public void BindingContext_Clear_Empty_Success() + { + var context = new BindingContext(); + context.Clear(); + Assert.Empty(context); + + // Clear again. + context.Clear(); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_Clear_NotEmpty_Success() + { + var context = new BindingContext(); + var source = new BindingSource(); + context.Add(new DataSource(), source.CurrencyManager); + + // Clear again. + context.Clear(); + Assert.Empty(context); + + context.Clear(); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_ClearCore_Empty_Success() + { + var context = new SubBindingContext(); + context.ClearCore(); + Assert.Empty(context); + + // Clear again. + context.ClearCore(); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_ClearCore_NotEmpty_Success() + { + var context = new SubBindingContext(); + var source = new BindingSource(); + context.Add(new DataSource(), source.CurrencyManager); + + // Clear again. + context.ClearCore(); + Assert.Empty(context); + + context.ClearCore(); + Assert.Empty(context); + } + + public static IEnumerable Contains_DataSource_TestData() + { + var context = new BindingContext(); + var source = new BindingSource(); + var dataSource = new DataSource(); + context.Add(dataSource, source.CurrencyManager); + yield return new object[] { context, dataSource, true }; + yield return new object[] { context, 1, false }; + } + + [Theory] + [MemberData(nameof(Contains_DataSource_TestData))] + public void BindingContext_Contains_DataSource_ReturnsExpected(BindingContext context, object dataSource, bool expected) + { + Assert.Equal(expected, context.Contains(dataSource)); + Assert.Equal(expected, context.Contains(dataSource, null)); + Assert.Equal(expected, context.Contains(dataSource, string.Empty)); + } + + + public static IEnumerable Contains_DataSourceDataMember_TestData() + { + var context = new BindingContext(); + var source = new BindingSource(); + var dataSource1 = new DataSource(); + var dataSource2 = new DataSource(); + context.Add(dataSource1, source.CurrencyManager); + Assert.NotNull(context[dataSource2, "Property"]); + + yield return new object[] { context, dataSource1, string.Empty, true }; + yield return new object[] { context, dataSource1, null, true }; + yield return new object[] { context, dataSource1, "Property", false }; + yield return new object[] { context, dataSource2, "Property", true }; + yield return new object[] { context, dataSource2, "property", true }; + yield return new object[] { context, dataSource2, "NoSuchProperty", false }; + yield return new object[] { context, dataSource2, string.Empty, true }; + yield return new object[] { context, dataSource2, null, true }; + yield return new object[] { context, 1, "Property", false }; + } + + [Theory] + [MemberData(nameof(Contains_DataSourceDataMember_TestData))] + public void BindingContext_Contains_DataSourceDataMember_ReturnsExpected(BindingContext context, object dataSource, string dataMember, bool expected) + { + Assert.Equal(expected, context.Contains(dataSource, dataMember)); + } + + [Fact] + public void BindingContext_Contains_NullDataSource_ThrowsArgumentNullException() + { + var context = new BindingContext(); + Assert.Throws("dataSource", () => context.Contains(null)); + Assert.Throws("dataSource", () => context.Contains(null, null)); + Assert.Throws("dataSource", () => context.Contains(null, string.Empty)); + } + + [Fact] + public void BindingContext_Item_GetNoSuchDataSource_AddsToCollection() + { + var context = new BindingContext(); + var dataSource = new DataSource(); + PropertyManager manager = Assert.IsType(context[dataSource]); + Assert.Same(dataSource, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Single(context); + Assert.Same(manager, context[dataSource]); + } + + [Fact] + public void BindingContext_Item_GetIListDataSource_AddsToCollection() + { + var context = new BindingContext(); + var dataSource = new List { 1, 2, 3}; + CurrencyManager manager = Assert.IsType(context[dataSource]); + Assert.Same(dataSource, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Single(context); + Assert.Same(manager, context[dataSource]); + } + + [Fact] + public void BindingContext_Item_GetArrayDataSource_AddsToCollection() + { + var context = new BindingContext(); + var dataSource = new int[] { 1, 2, 3}; + CurrencyManager manager = Assert.IsType(context[dataSource]); + Assert.Same(dataSource, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Single(context); + Assert.Same(manager, context[dataSource]); + } + + [Fact] + public void BindingContext_Item_GetIListSourceDataSource_AddsToCollection() + { + var context = new BindingContext(); + var dataSource = new List { 1, 2, 3}; + var mockIListSource = new Mock(MockBehavior.Strict); + mockIListSource + .Setup(s => s.GetList()) + .Returns(dataSource); + + CurrencyManager manager = Assert.IsType(context[mockIListSource.Object]); + Assert.Same(dataSource, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Single(context); + Assert.Same(manager, context[mockIListSource.Object]); + } + + [Fact] + public void BindingContext_Item_GetIListSourceDataSourceReturningNull_ThrowsArgumentNullException() + { + var context = new BindingContext(); + var mockIListSource = new Mock(MockBehavior.Strict); + mockIListSource + .Setup(s => s.GetList()) + .Returns((IList)null); + + Assert.Throws("dataSource", () => context[mockIListSource.Object]); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_Item_GetWithICurrencyManagerProvider_DoesNotAddToCollection() + { + var context = new BindingContext(); + CurrencyManager manager = new BindingSource().CurrencyManager; + var mockCurrencyManagerProvider = new Mock(); + mockCurrencyManagerProvider + .Setup(p => p.GetRelatedCurrencyManager("dataMember")) + .Returns(manager); + + Assert.Same(manager, context[mockCurrencyManagerProvider.Object, "dataMember"]); + Assert.Empty(context); + } + + [Fact] + public void BindingContext_Item_GetWithNullICurrencyManagerProvider_AddsToCollection() + { + var context = new BindingContext(); + var mockCurrencyManagerProvider = new Mock(); + mockCurrencyManagerProvider + .Setup(p => p.GetRelatedCurrencyManager("dataMember")) + .Returns((CurrencyManager)null); + + PropertyManager manager = Assert.IsType(context[mockCurrencyManagerProvider.Object]); + Assert.Single(context); + Assert.Same(manager, context[mockCurrencyManagerProvider.Object]); + } + + public static IEnumerable Item_DataSourceWithDataMember_TestData() + { + var dataSource = new ParentDataSource + { + ParentProperty = new DataSource + { + Property = 1 + } + }; + yield return new object[] { dataSource, "ParentProperty", dataSource.ParentProperty, 2 }; + yield return new object[] { dataSource, "ParentProperty.Property", dataSource.ParentProperty.Property, 3 }; + yield return new object[] { dataSource, "parentproperty", dataSource.ParentProperty, 2 }; + yield return new object[] { dataSource, "parentproperty.property", dataSource.ParentProperty.Property, 3 }; + } + + [Theory] + [MemberData(nameof(Item_DataSourceWithDataMember_TestData))] + public void BindingContext_Item_GetNoSuchDataSourceWithDataMember_AddsToCollection(object dataSource, string dataMember, object expectedCurrent, int expectedCount) + { + var context = new BindingContext(); + PropertyManager manager = Assert.IsAssignableFrom(context[dataSource, dataMember]); + Assert.Equal(expectedCurrent, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Equal(expectedCount, ((ICollection)context).Count); + Assert.Same(manager, context[dataSource, dataMember]); + Assert.Same(manager, context[dataSource, dataMember.ToLower()]); + } + + [Fact] + public void BindingContext_Item_GetWithAddedDataSourceWithDataMember_ThrowsArgumentException() + { + var context = new BindingContext(); + var source = new BindingSource(); + var dataSource = new DataSource(); + context.Add(dataSource, source.CurrencyManager); + Assert.Throws(null, () => context[dataSource, "Property"]); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void BindingContext_Item_GetNoSuchDataSourceNullOrEmptyMember_AddsToCollection(string dataMember) + { + var context = new SubBindingContext(); + var dataSource = new DataSource(); + PropertyManager manager = Assert.IsType(context[dataSource, dataMember]); + Assert.Single(context); + Assert.Same(manager, context[dataSource]); + Assert.Same(manager, context[dataSource, string.Empty]); + Assert.Same(manager, context[dataSource]); + Assert.Same(manager, context[dataSource, null]); + } + + [Theory] + [InlineData("NoSuchProperty")] + [InlineData(".")] + [InlineData("..")] + [InlineData("ParentProperty.")] + [InlineData("ParentProperty.NoSuchProperty")] + public void BindingContext_Item_GetNoSuchDataSourceNoSuchDataMember_ThrowsArgumentException(string dataMember) + { + var context = new SubBindingContext(); + var dataSource = new ParentDataSource(); + Assert.Throws(null, () => context[dataSource, dataMember]); + } + + [Fact] + public void BindingContext_Item_GetIListWithDataMemberReturningIList_AddsToCollection() + { + var context = new BindingContext(); + var list = new List { 1, 2, 3}; + var dataSource = new IListDataSource(); + dataSource.Property = list; + + CurrencyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(list, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Equal(2, ((ICollection)context).Count); + Assert.Same(manager, context[dataSource, "Property"]); + } + + [Fact] + public void BindingContext_Item_GetIListWithDataMemberReturningNonIList_AddsToCollection() + { + var context = new BindingContext(); + var list = new List { 1, 2, 3}; + var dataSource = new ObjectDataSource(); + dataSource.Property = list; + + PropertyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(list, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Equal(2, ((ICollection)context).Count); + Assert.Same(manager, context[dataSource, "Property"]); + } + + [Fact] + public void BindingContext_Item_GetArrayWithDataMember_AddsToCollection() + { + var context = new BindingContext(); + var list = new int[] { 1, 2, 3}; + var dataSource = new IListDataSource(); + dataSource.Property = list; + + CurrencyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(list, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Equal(2, ((ICollection)context).Count); + Assert.Same(manager, context[dataSource, "Property"]); + } + + [Fact] + public void BindingContext_Item_GetIListSourceDataSourceWithDataMemberReturningIList_AddsToCollection() + { + var context = new BindingContext(); + var list = new List { 1, 2, 3}; + var dataSource = new IListDataSource(); + var mockIListSource = new Mock(MockBehavior.Strict); + mockIListSource + .Setup(s => s.GetList()) + .Returns(list); + var mockIList = mockIListSource.As(); + dataSource.Property = mockIList.Object; + + CurrencyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(list, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Equal(2, ((ICollection)context).Count); + Assert.Same(manager, context[dataSource, "Property"]); + } + + [Fact] + public void BindingContext_Item_GetIListSourceDataSourceWithDataMemberReturningNonIList_AddsToCollection() + { + var context = new BindingContext(); + var list = new List { 1, 2, 3}; + var dataSource = new IListSourceDataSource(); + var mockIListSource = new Mock(MockBehavior.Strict); + mockIListSource + .Setup(s => s.GetList()) + .Returns(list); + dataSource.Property = mockIListSource.Object; + + PropertyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(mockIListSource.Object, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Equal(2, ((ICollection)context).Count); + Assert.Same(manager, context[dataSource, "Property"]); + } + + [Fact] + public void BindingContext_Item_GetIListSourceDataSourceWithDataMemberReturningIListNull_ThrowsArgumentNullException() + { + var context = new BindingContext(); + var dataSource = new IListDataSource(); + var mockIListSource = new Mock(MockBehavior.Strict); + mockIListSource + .Setup(s => s.GetList()) + .Returns((IList)null); + var mockIList = mockIListSource.As(); + dataSource.Property = mockIList.Object; + + Assert.Throws("dataSource", () => context[dataSource, "Property"]); + + // Does, however, add the parent. + PropertyManager parentManager = Assert.IsType(Assert.IsType(Assert.IsType(Assert.Single(context)).Value).Target); + Assert.Same(dataSource, parentManager.Current); + } + + [Fact] + public void BindingContext_Item_GetIListSourceDataSourceWithDataMemberReturningNonIListNull_AddsToCollection() + { + var context = new BindingContext(); + var dataSource = new IListSourceDataSource(); + var mockIListSource = new Mock(MockBehavior.Strict); + mockIListSource + .Setup(s => s.GetList()) + .Returns((IList)null); + dataSource.Property = mockIListSource.Object; + + PropertyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(mockIListSource.Object, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Equal(2, ((ICollection)context).Count); + Assert.Same(manager, context[dataSource, "Property"]); + } + + [Fact] + public void BindingContext_Item_GetWithNullWeakReferenceTarget_Success() + { + var context = new BindingContext(); + var dataSource = new DataSource(); + PropertyManager manager = Assert.IsType(context[dataSource]); + WeakReference reference = Assert.IsType(Assert.IsType(Assert.Single(context)).Value); + Assert.Same(manager, reference.Target); + + // Simulate a GC by assigning the weak reference to null. + reference.Target = null; + + // Now get the new manager and verify it. + PropertyManager newManager = Assert.IsType(context[dataSource]); + Assert.NotSame(manager, newManager); + Assert.Same(dataSource, newManager.Current); + Assert.Equal(1, newManager.Count); + Assert.Equal(0, newManager.Position); + Assert.Single(context); + Assert.Same(newManager, context[dataSource]); + } + + [Fact] + public void BindingContext_Item_GetNullDataSource_ThrowsArgumentNullException() + { + var context = new SubBindingContext(); + Assert.Throws("dataSource", () => context[null]); + Assert.Throws("dataSource", () => context[null, null]); + Assert.Throws("dataSource", () => context[null, string.Empty]); + } + + [Fact] + public void BindingContext_CollectionChanged_Add_ThrowsNotImplementedException() + { + var context = new BindingContext(); + CollectionChangeEventHandler handler = (sender, e) => { }; + Assert.Throws(() => context.CollectionChanged += handler); + } + + [Fact] + public void BindingContext_CollectionChanged_Remove_Nop() + { + var context = new BindingContext(); + CollectionChangeEventHandler handler = (sender, e) => { }; + context.CollectionChanged -= handler; + } + + [Fact] + public void BindingContext_OnCollectionChanged_Invoke_Nop() + { + var context = new SubBindingContext(); + CollectionChangeEventHandler handler = (sender, e) => { }; + context.OnCollectionChanged(null); + } + + public static IEnumerable Equals_TestData() + { + var context1 = new BindingContext(); + var context2 = new BindingContext(); + var context3 = new BindingContext(); + var context4 = new BindingContext(); + var source1 = new BindingSource(); + var source2 = new BindingSource(); + var dataSource1 = new DataSource(); + var dataSource2 = new DataSource(); + + context1.Add(dataSource1, source1.CurrencyManager); + context2.Add(dataSource1, source1.CurrencyManager); + context3.Add(dataSource2, source1.CurrencyManager); + context4.Add(dataSource2, source2.CurrencyManager); + + yield return new object[] { Assert.Single(context1), Assert.Single(context1), true }; + yield return new object[] { Assert.Single(context1), Assert.Single(context2), true }; + yield return new object[] { Assert.Single(context1), Assert.Single(context3), false }; + yield return new object[] { Assert.Single(context1), Assert.Single(context4), false }; + + yield return new object[] { Assert.Single(context1), new object(), false }; + yield return new object[] { Assert.Single(context1), null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void BindingContext_KeyEquals_Invoke_ReturnsExpected(DictionaryEntry entry, object other, bool expected) + { + if (other is DictionaryEntry otherEntry) + { + Assert.Equal(expected, entry.Key.Equals(otherEntry.Key)); + } + else + { + Assert.Equal(expected, entry.Key.Equals(other)); + } + } + + [Fact] + public void BindingContext_UpdateBinding_NewBindingWithoutDataMember_Success() + { + var context = new BindingContext(); + var dataSource = new DataSource(); + var binding = new Binding(null, dataSource, "dataMember"); + + BindingContext.UpdateBinding(context, binding); + Assert.Single(context); + + PropertyManager manager = Assert.IsType(context[dataSource]); + Assert.Same(binding, Assert.Single(manager.Bindings)); + Assert.Same(dataSource, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Same(manager, binding.BindingManagerBase); + } + + [Fact] + public void BindingContext_UpdateBinding_NewListBindingWithoutDataMember_Success() + { + var context = new BindingContext(); + var dataSource = new List { 1, 2, 3 }; + var binding = new Binding(null, dataSource, "dataMember"); + + BindingContext.UpdateBinding(context, binding); + Assert.Single(context); + + CurrencyManager manager = Assert.IsType(context[dataSource]); + Assert.Same(binding, Assert.Single(manager.Bindings)); + Assert.Same(dataSource, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Same(manager, binding.BindingManagerBase); + } + + [Fact] + public void BindingContext_UpdateBinding_NewBindingWithDataMember_Success() + { + var context = new BindingContext(); + var dataSource = new DataSource { Property = 1 }; + var binding = new Binding(null, dataSource, "Property.ignored"); + + BindingContext.UpdateBinding(context, binding); + Assert.Equal(2, ((ICollection)context).Count); + + PropertyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(binding, Assert.Single(manager.Bindings)); + Assert.Equal(1, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Same(manager, binding.BindingManagerBase); + } + + [Fact] + public void BindingContext_UpdateBinding_NewListBindingWithDataMember_Success() + { + var context = new BindingContext(); + var list = new List { 1, 2, 3 }; + var dataSource = new IListDataSource { Property = list }; + var binding = new Binding(null, dataSource, "Property.ignore"); + + BindingContext.UpdateBinding(context, binding); + Assert.Equal(2, ((ICollection)context).Count); + + CurrencyManager manager = Assert.IsAssignableFrom(context[dataSource, "Property"]); + Assert.Same(binding, Assert.Single(manager.Bindings)); + Assert.Same(list, manager.List); + Assert.Equal(1, manager.Current); + Assert.Equal(3, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Same(manager, binding.BindingManagerBase); + } + + [Fact] + public void BindingContext_UpdateBinding_UpdateBindingWithoutDataMember_Success() + { + var context1 = new BindingContext(); + var context2 = new BindingContext(); + var dataSource = new DataSource(); + var binding = new Binding(null, dataSource, "dataMember"); + + BindingContext.UpdateBinding(context1, binding); + Assert.Single(context1); + Assert.Empty(context2); + + // Update. + BindingContext.UpdateBinding(context2, binding); + Assert.Single(context1); + Assert.Single(context2); + + PropertyManager manager1 = Assert.IsType(context1[dataSource]); + Assert.Empty(manager1.Bindings); + Assert.Same(dataSource, manager1.Current); + Assert.Equal(1, manager1.Count); + Assert.Equal(0, manager1.Position); + + PropertyManager manager2 = Assert.IsType(context2[dataSource]); + Assert.Same(binding, Assert.Single(manager2.Bindings)); + Assert.Same(dataSource, manager2.Current); + Assert.Equal(1, manager2.Count); + Assert.Equal(0, manager2.Position); + Assert.Same(manager2, binding.BindingManagerBase); + } + + [Fact] + public void BindingContext_UpdateBinding_UpdateListBindingWithoutDataMember_Success() + { + var context1 = new BindingContext(); + var context2 = new BindingContext(); + var dataSource = new List { 1, 2, 3 }; + var binding = new Binding(null, dataSource, "dataMember"); + + BindingContext.UpdateBinding(context1, binding); + Assert.Single(context1); + Assert.Empty(context2); + + // Update. + BindingContext.UpdateBinding(context2, binding); + Assert.Single(context1); + Assert.Single(context2); + + CurrencyManager manager1 = Assert.IsType(context1[dataSource]); + Assert.Empty(manager1.Bindings); + Assert.Same(dataSource, manager1.List); + Assert.Equal(1, manager1.Current); + Assert.Equal(3, manager1.Count); + Assert.Equal(0, manager1.Position); + + CurrencyManager manager2 = Assert.IsType(context2[dataSource]); + Assert.Same(binding, Assert.Single(manager2.Bindings)); + Assert.Same(dataSource, manager2.List); + Assert.Equal(1, manager2.Current); + Assert.Equal(3, manager2.Count); + Assert.Equal(0, manager2.Position); + Assert.Same(manager2, binding.BindingManagerBase); + } + + [Fact] + public void BindingContext_UpdateBinding_NullBindingContext_Success() + { + var context = new BindingContext(); + var dataSource = new DataSource(); + var binding = new Binding(null, dataSource, "dataMember"); + + // Without binding manager. + BindingContext.UpdateBinding(null, binding); + Assert.Null(binding.BindingManagerBase); + + // With binding manager. + BindingContext.UpdateBinding(context, binding); + Assert.Single(context); + Assert.NotNull(binding.BindingManagerBase); + + BindingContext.UpdateBinding(null, binding); + Assert.Single(context); + + PropertyManager manager = Assert.IsAssignableFrom(context[dataSource]); + Assert.Empty(manager.Bindings); + Assert.Equal(dataSource, manager.Current); + Assert.Equal(1, manager.Count); + Assert.Equal(0, manager.Position); + Assert.Null(binding.BindingManagerBase); + } + + [Fact] + public void BindingContext_UpdateBinding_NullBinding_ThrowsArgumentNullException() + { + Assert.Throws("binding", () => BindingContext.UpdateBinding(new BindingContext(), null)); + } + + private class ParentDataSource + { + public DataSource ParentProperty { get; set; } + } + + private class DataSource + { + public int Property { get; set; } + } + + private class IListDataSource + { + public IList Property { get; set; } + } + + private class IListSourceDataSource + { + public IListSource Property { get; set; } + } + + private class ObjectDataSource + { + public object Property { get; set; } + } + + private class SubBindingContext : BindingContext + { + public new void AddCore(object dataSource, BindingManagerBase listManager) + { + base.AddCore(dataSource, listManager); + } + + public new void RemoveCore(object dataSource) + { + base.RemoveCore(dataSource); + } + + public new void ClearCore() + { + base.ClearCore(); + } + + public new void OnCollectionChanged(CollectionChangeEventArgs ccevent) + { + base.OnCollectionChanged(ccevent); + } + } + } +}