diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs new file mode 100644 index 0000000000..dd5f8af61b --- /dev/null +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -0,0 +1,93 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Collections +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Linq; + + public class PerspexReadOnlyListView : IReadOnlyPerspexList, IDisposable + { + private IReadOnlyPerspexList inner; + + private Func convert; + + public PerspexReadOnlyListView( + IReadOnlyPerspexList inner, + Func convert) + { + this.inner = inner; + this.convert = convert; + this.inner.CollectionChanged += this.InnerCollectionChanged; + } + + public TOut this[int index] + { + get { return this.convert(this.inner[index]); } + } + + public int Count + { + get { return this.inner.Count; } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + public void Dispose() + { + this.inner.CollectionChanged -= this.InnerCollectionChanged; + } + + public IEnumerator GetEnumerator() + { + return this.inner.Select(convert).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + private IList ConvertList(IList list) + { + return list.Cast().Select(this.convert).ToList(); + } + + private void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (this.CollectionChanged != null) + { + NotifyCollectionChangedEventArgs ev; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + this.ConvertList(e.NewItems), + e.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Remove: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + this.ConvertList(e.OldItems), + e.NewStartingIndex); + break; + default: + throw new NotSupportedException("Action not yet implemented."); + } + + this.CollectionChanged(this, ev); + } + } + } +} diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index 1ddb527ce9..6959c65d9d 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -36,6 +36,7 @@ + diff --git a/Perspex.Controls.UnitTests/ItemsControlTests.cs b/Perspex.Controls.UnitTests/ItemsControlTests.cs new file mode 100644 index 0000000000..72a0f98a33 --- /dev/null +++ b/Perspex.Controls.UnitTests/ItemsControlTests.cs @@ -0,0 +1,294 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.UnitTests +{ + using System; + using System.Collections.Specialized; + using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Perspex.Collections; + using Perspex.Controls; + using Perspex.Controls.Presenters; + using Perspex.Layout; + using Perspex.Platform; + using Perspex.Styling; + using Perspex.VisualTree; + using Ploeh.AutoFixture; + using Ploeh.AutoFixture.AutoMoq; + using Splat; + + [TestClass] + public class ItemsControlTests + { + [TestMethod] + public void Template_Should_Be_Instantiated() + { + using (var ctx = this.RegisterServices()) + { + var target = new ItemsControl(); + target.Items = new[] { "Foo" }; + target.Template = this.GetTemplate(); + + target.Measure(new Size(100, 100)); + + var child = ((IVisual)target).VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(Border)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(ItemsPresenter)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(StackPanel)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(TextBlock)); + } + } + + [TestMethod] + public void Templated_Children_Should_Be_Styled() + { + using (var ctx = this.RegisterServices()) + { + var root = new TestRoot(); + var target = new ItemsControl(); + var styler = new Mock(); + + Locator.CurrentMutable.Register(() => styler.Object, typeof(IStyler)); + target.Items = new[] { "Foo" }; + target.Template = this.GetTemplate(); + root.Content = target; + + target.ApplyTemplate(); + + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + } + } + + [TestMethod] + public void ItemsPresenter_And_Panel_Should_Have_TemplatedParent_Set() + { + var target = new ItemsControl(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + + var presenter = target.GetTemplateControls().OfType().Single(); + var panel = target.GetTemplateControls().OfType().Single(); + + Assert.AreEqual(target, presenter.TemplatedParent); + Assert.AreEqual(target, panel.TemplatedParent); + } + + [TestMethod] + public void Item_Should_Have_TemplatedParent_Set_To_Null() + { + var target = new ItemsControl(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + + var panel = target.GetTemplateControls().OfType().Single(); + var item = (TextBlock)panel.GetVisualChildren().First(); + + Assert.IsNull(item.TemplatedParent); + } + + [TestMethod] + public void Control_Item_Should_Set_Control_Parent() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + Assert.AreEqual(target, child.Parent); + Assert.AreEqual(target, ((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Clearing_Control_Item_Should_Clear_Child_Controls_Parent() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + target.Items = null; + + Assert.IsNull(child.Parent); + Assert.IsNull(((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Control_Item_Should_Make_Control_Appear_In_LogicalChildren() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + CollectionAssert.AreEqual(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); + } + + [TestMethod] + public void String_Item_Should_Make_TextBlock_Appear_In_LogicalChildren() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + + var logical = (ILogical)target; + Assert.AreEqual(1, logical.LogicalChildren.Count); + Assert.IsInstanceOfType(logical.LogicalChildren[0], typeof(TextBlock)); + } + + [TestMethod] + public void Setting_Items_To_Null_Should_Remove_LogicalChildren() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Items = null; + + CollectionAssert.AreEqual(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); + } + + [TestMethod] + public void Setting_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var child = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + target.Items = new[] { child }; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Setting_Items_To_Null_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var child = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + target.Items = null; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Changing_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var child = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; + + target.Items = new[] { "Foo" }; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Adding_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var items = new PerspexList { "Foo" }; + var called = false; + + target.Template = this.GetTemplate(); + target.Items = items; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + items.Add("Bar"); + + Assert.IsTrue(called); + } + + [TestMethod] + public void Removing_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var items = new PerspexList { "Foo", "Bar" }; + var called = false; + + target.Template = this.GetTemplate(); + target.Items = items; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + items.Remove("Bar"); + + Assert.IsTrue(called); + } + + private ControlTemplate GetTemplate() + { + return ControlTemplate.Create(parent => + { + return new Border + { + Background = new Perspex.Media.SolidColorBrush(0xffffffff), + Content = new ItemsPresenter + { + Id = "presenter", + [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + } + }; + }); + } + + private IDisposable RegisterServices() + { + var result = Locator.CurrentMutable.WithResolver(); + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var renderInterface = fixture.Create(); + Locator.CurrentMutable.RegisterConstant(renderInterface, typeof(IPlatformRenderInterface)); + return result; + } + } +} diff --git a/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index c16687597e..35214ff6b9 100644 --- a/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -64,6 +64,7 @@ +