// Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reactive.Linq; using Perspex.Collections; using Perspex.Diagnostics; using Perspex.Markup.Data; using Xunit; namespace Perspex.Markup.UnitTests.Data { public class ExpressionObserverTests_Indexer { [Fact] public async void Should_Get_Array_Value() { var data = new { Foo = new [] { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1]"); var result = await target.Take(1); Assert.Equal("bar", result); } [Fact] public async void Should_Get_MultiDimensional_Array_Value() { var data = new { Foo = new[,] { { "foo", "bar" }, { "baz", "qux" } } }; var target = new ExpressionObserver(data, "Foo[1, 1]"); var result = await target.Take(1); Assert.Equal("qux", result); } [Fact] public async void Should_Get_Value_For_String_Indexer() { var data = new { Foo = new Dictionary { { "foo", "bar" }, { "baz", "qux" } } }; var target = new ExpressionObserver(data, "Foo[foo]"); var result = await target.Take(1); Assert.Equal("bar", result); } [Fact] public async void Should_Get_Value_For_Non_String_Indexer() { var data = new { Foo = new Dictionary { { 1.0, "bar" }, { 2.0, "qux" } } }; var target = new ExpressionObserver(data, "Foo[1.0]"); var result = await target.Take(1); Assert.Equal("bar", result); } [Fact] public async void Array_Out_Of_Bounds_Should_Return_UnsetValue() { var data = new { Foo = new[] { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[2]"); var result = await target.Take(1); Assert.Equal(PerspexProperty.UnsetValue, result); } [Fact] public async void Array_With_Wrong_Dimensions_Should_Return_UnsetValue() { var data = new { Foo = new[] { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1,2]"); var result = await target.Take(1); Assert.Equal(PerspexProperty.UnsetValue, result); } [Fact] public async void List_Out_Of_Bounds_Should_Return_UnsetValue() { var data = new { Foo = new List { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[2]"); var result = await target.Take(1); Assert.Equal(PerspexProperty.UnsetValue, result); } [Fact] public async void Should_Get_List_Value() { var data = new { Foo = new List { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1]"); var result = await target.Take(1); Assert.Equal("bar", result); } [Fact] public void Should_Track_INCC_Add() { var data = new { Foo = new PerspexList { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[2]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) { data.Foo.Add("baz"); } Assert.Equal(new[] { PerspexProperty.UnsetValue, "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); } [Fact] public void Should_Track_INCC_Remove() { var data = new { Foo = new PerspexList { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[0]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) { data.Foo.RemoveAt(0); } Assert.Equal(new[] { "foo", "bar" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); } [Fact] public void Should_Track_INCC_Replace() { var data = new { Foo = new PerspexList { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) { data.Foo[1] = "baz"; } Assert.Equal(new[] { "bar", "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); } [Fact] public void Should_Track_INCC_Move() { // Using ObservableCollection here because PerspexList does not yet have a Move // method, but even if it did we need to test with ObservableCollection as well // as PerspexList as it implements PropertyChanged as an explicit interface event. var data = new { Foo = new ObservableCollection { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1]"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); data.Foo.Move(0, 1); Assert.Equal(new[] { "bar", "foo" }, result); } [Fact] public void Should_Track_INCC_Reset() { var data = new { Foo = new PerspexList { "foo", "bar" } }; var target = new ExpressionObserver(data, "Foo[1]"); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); data.Foo.Clear(); Assert.Equal(new[] { "bar", PerspexProperty.UnsetValue }, result); } [Fact] public void Should_Track_NonIntegerIndexer() { var data = new { Foo = new NonIntegerIndexer() }; data.Foo["foo"] = "bar"; data.Foo["baz"] = "qux"; var target = new ExpressionObserver(data, "Foo[foo]"); var result = new List(); using (var sub = target.Subscribe(x => result.Add(x))) { data.Foo["foo"] = "bar2"; } var expected = new[] { "bar", "bar2" }; Assert.Equal(expected, result); Assert.Equal(0, data.Foo.SubscriptionCount); } [Fact] public void Should_Not_Keep_Source_Alive_ObservableCollection() { Func> run = () => { var source = new { Foo = new PerspexList { "foo", "bar" } }; var target = new ExpressionObserver(source, "Foo"); return Tuple.Create(target, new WeakReference(source.Foo)); }; var result = run(); result.Item1.Subscribe(x => { }); GC.Collect(); Assert.Null(result.Item2.Target); } [Fact] public void Should_Not_Keep_Source_Alive_NonIntegerIndexer() { Func> run = () => { var source = new NonIntegerIndexer(); var target = new ExpressionObserver(source, "Foo"); return Tuple.Create(target, new WeakReference(source)); }; var result = run(); result.Item1.Subscribe(x => { }); GC.Collect(); Assert.Null(result.Item2.Target); } private class NonIntegerIndexer : NotifyingBase { private Dictionary storage = new Dictionary(); public string this[string key] { get { return storage[key]; } set { storage[key] = value; RaisePropertyChanged(CommonPropertyNames.IndexerName); } } } } }