diff --git a/Perspex.sln b/Perspex.sln index 22b196d2c8..3b97ab21cf 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -96,6 +96,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.HtmlRenderer", "src EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup", "src\Markup\Perspex.Markup\Perspex.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.UnitTests", "tests\Perspex.Markup.UnitTests\Perspex.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 @@ -235,6 +239,14 @@ Global {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.Build.0 = Release|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6417E941-21BC-467B-A771-0DE389353CE6}.Release|Any CPU.Build.0 = Release|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -260,5 +272,7 @@ Global {54F237D5-A70A-4752-9656-0C70B1A7B047} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} {FB05AC90-89BA-4F2F-A924-F37875FB547C} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} {E4D9629C-F168-4224-3F51-A5E482FFBC42} = {A689DEF5-D50F-4975-8B72-124C9EB54066} + {6417E941-21BC-467B-A771-0DE389353CE6} = {8B6A8209-894F-4BA1-B880-965FD453982C} + {8EF392D5-1416-45AA-9956-7CBBC3229E8A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal diff --git a/Perspex.v2.ncrunchsolution b/Perspex.v2.ncrunchsolution index fe4feb2cb6..56e7a57022 100644 Binary files a/Perspex.v2.ncrunchsolution and b/Perspex.v2.ncrunchsolution differ diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs new file mode 100644 index 0000000000..6e21887dea --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNode.cs @@ -0,0 +1,103 @@ +// 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.Reactive; +using System.Reactive.Subjects; + +namespace Perspex.Markup.Binding +{ + public abstract class ExpressionNode : IObservable + { + private object _target; + + private Subject _subject; + + private ExpressionValue _value = ExpressionValue.None; + + public ExpressionNode Next + { + get; + set; + } + + public object Target + { + get { return _target; } + set + { + if (_target != null) + { + Unsubscribe(_target); + } + + _target = value; + + if (_target != null) + { + Subscribe(_target); + } + else + { + CurrentValue = ExpressionValue.None; + } + + if (Next != null) + { + Next.Target = CurrentValue.Value; + } + } + } + + public ExpressionValue CurrentValue + { + get + { + return _value; + } + + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _value = value; + + if (Next != null) + { + Next.Target = value.Value; + } + + if (_subject != null) + { + _subject.OnNext(value); + } + } + } + + public IDisposable Subscribe(IObserver observer) + { + if (Next != null) + { + return Next.Subscribe(observer); + } + else + { + if (_subject == null) + { + _subject = new Subject(); + } + + observer.OnNext(CurrentValue); + return _subject.Subscribe(observer); + } + } + + protected abstract void Subscribe(object target); + + protected abstract void Unsubscribe(object target); + + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs new file mode 100644 index 0000000000..ebd4b85baf --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Perspex.Markup.Binding +{ + public class ExpressionNodeBuilder + { + public static IList Build(string expression) + { + if (string.IsNullOrWhiteSpace(expression)) + { + throw new ArgumentException("'expression' may not be empty."); + } + + var syntaxTree = CSharpSyntaxTree.ParseText(expression, new CSharpParseOptions(kind: SourceCodeKind.Interactive)); + var syntaxRoot = syntaxTree.GetRoot(); + var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault() as ExpressionStatementSyntax; + + if (syntax != null) + { + var result = new List(); + + foreach (SyntaxNode node in syntax.ChildNodes()) + { + var identifier = node as IdentifierNameSyntax; + var memberAccess = node as MemberAccessExpressionSyntax; + + if (identifier != null) + { + result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText)); + } + else if (memberAccess != null) + { + Build(memberAccess, result); + } + } + + for (int i = 0; i < result.Count - 1; ++i) + { + result[i].Next = result[i + 1]; + } + + return result; + } + else + { + throw new Exception($"Invalid expression: {expression}"); + } + } + + private static void Build(MemberAccessExpressionSyntax syntax, IList result) + { + foreach (SyntaxNode node in syntax.ChildNodes()) + { + var identifier = node as IdentifierNameSyntax; + var memberAccess = node as MemberAccessExpressionSyntax; + + if (identifier != null) + { + result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText)); + } + else if (memberAccess != null) + { + Build(memberAccess, result); + } + } + } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs new file mode 100644 index 0000000000..1ddd23e372 --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs @@ -0,0 +1,55 @@ +// 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.Linq; +using System.Reactive; +using System.Reactive.Disposables; + +namespace Perspex.Markup.Binding +{ + public class ExpressionObserver : ObservableBase + { + private int _count; + + public ExpressionObserver(object root, string expression) + { + Root = root; + Nodes = ExpressionNodeBuilder.Build(expression); + } + + public object Root { get; } + + public IList Nodes { get; } + + protected override IDisposable SubscribeCore(IObserver observer) + { + IncrementCount(); + + var subscription = Nodes[0].Subscribe(observer); + + return Disposable.Create(() => + { + DecrementCount(); + subscription.Dispose(); + }); + } + + private void IncrementCount() + { + if (_count++ == 0) + { + Nodes[0].Target = Root; + } + } + + private void DecrementCount() + { + if (--_count == 0) + { + Nodes[0].Target = null; + } + } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs b/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs new file mode 100644 index 0000000000..7579f98db4 --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/ExpressionValue.cs @@ -0,0 +1,26 @@ +// 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; + +namespace Perspex.Markup.Binding +{ + public class ExpressionValue + { + public static readonly ExpressionValue None = new ExpressionValue(); + + public ExpressionValue(object value) + { + HasValue = true; + Value = value; + } + + private ExpressionValue() + { + HasValue = false; + } + + public bool HasValue { get; } + public object Value { get; } + } +} diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs new file mode 100644 index 0000000000..c2ad61bbb4 --- /dev/null +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -0,0 +1,67 @@ +// 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.ComponentModel; +using System.Reflection; + +namespace Perspex.Markup.Binding +{ + public class PropertyAccessorNode : ExpressionNode + { + private PropertyInfo _propertyInfo; + + public PropertyAccessorNode(string propertyName) + { + PropertyName = propertyName; + } + + public string PropertyName { get; } + + protected override void Subscribe(object target) + { + var result = ExpressionValue.None; + + if (target != null) + { + _propertyInfo = target.GetType().GetTypeInfo().GetDeclaredProperty(PropertyName); + + if (_propertyInfo != null) + { + result = new ExpressionValue(_propertyInfo.GetValue(target)); + + var inpc = target as INotifyPropertyChanged; + + if (inpc != null) + { + inpc.PropertyChanged += PropertyChanged; + } + } + } + else + { + _propertyInfo = null; + } + + CurrentValue = result; + } + + private void PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == PropertyName) + { + CurrentValue = new ExpressionValue(_propertyInfo.GetValue(Target)); + } + } + + protected override void Unsubscribe(object target) + { + var inpc = target as INotifyPropertyChanged; + + if (inpc != null) + { + inpc.PropertyChanged -= PropertyChanged; + } + } + } +} diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj new file mode 100644 index 0000000000..5db8937f65 --- /dev/null +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -0,0 +1,93 @@ + + + + + 11.0 + Debug + AnyCPU + {6417E941-21BC-467B-A771-0DE389353CE6} + Library + Properties + Perspex.Markup + Perspex.Markup + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + ..\..\..\packages\Microsoft.CodeAnalysis.Common.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll + True + + + ..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.1.0-beta1-20150812-01\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll + True + + + ..\..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True + + + ..\..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll + True + + + ..\..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll + True + + + ..\..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll + True + + + ..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll + True + + + ..\..\..\packages\System.Reflection.Metadata.1.1.0-alpha-00009\lib\portable-net45+win8\System.Reflection.Metadata.dll + True + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs b/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c3a8edb54e --- /dev/null +++ b/src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Markup")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Markup")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Markup/Perspex.Markup/packages.config b/src/Markup/Perspex.Markup/packages.config new file mode 100644 index 0000000000..aa2968fe54 --- /dev/null +++ b/src/Markup/Perspex.Markup/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs new file mode 100644 index 0000000000..cdbbc1d1d3 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs @@ -0,0 +1,29 @@ +// 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 Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionNodeBuilderTests + { + [Fact] + public void Should_Build_Single_Property() + { + var result = ExpressionNodeBuilder.Build("Foo"); + + Assert.Equal(1, result.Count); + Assert.IsType(result[0]); + } + + [Fact] + public void Should_Build_Property_Chain() + { + var result = ExpressionNodeBuilder.Build("Foo.Bar.Baz"); + + Assert.Equal(3, result.Count); + Assert.IsType(result[0]); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs new file mode 100644 index 0000000000..22770868b5 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs @@ -0,0 +1,163 @@ +// 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.Reactive.Linq; +using Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests + { + [Fact] + public async void Should_Get_Simple_Property_Value() + { + var data = new { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("foo", result.Value); + } + + [Fact] + public async void Should_Get_Simple_Property_Chain() + { + var data = new { Foo = new { Bar = new { Baz = "baz" } } }; + var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var result = await target.Take(1); + + Assert.True(result.HasValue); + Assert.Equal("baz", result.Value); + } + + [Fact] + public async void Should_Not_Have_Value_For_Broken_Chain() + { + var data = new { Foo = new { Bar = 1 } }; + var target = new ExpressionObserver(data, "Foo.Bar.Baz"); + var result = await target.Take(1); + + Assert.False(result.HasValue); + } + + [Fact] + public void Should_Track_Simple_Property_Value() + { + var data = new Class1 { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Foo = "bar"; + + Assert.Equal(new[] { "foo", "bar" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + } + + [Fact] + public void Should_Track_End_Of_Property_Chain_Changing() + { + var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Class2.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + data.Class2.Bar = "baz"; + + Assert.Equal(new[] { "bar", "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Class2.SubscriptionCount); + } + + [Fact] + public void Should_Track_Middle_Of_Property_Chain_Changing() + { + var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Class2.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + var old = data.Class2; + data.Class2 = new Class2 { Bar = "baz" }; + + Assert.Equal(new[] { "bar", "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, old.SubscriptionCount); + } + + [Fact] + public void Should_Track_Middle_Of_Property_Chain_Breaking_Then_Mending() + { + var data = new Class1 { Class2 = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Class2.Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x.Value)); + var old = data.Class2; + data.Class2 = null; + data.Class2 = new Class2 { Bar = "baz" }; + + Assert.Equal(new[] { "bar", null, "baz" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + Assert.Equal(0, data.Class2.SubscriptionCount); + Assert.Equal(0, old.SubscriptionCount); + } + + private class Class1 : NotifyingBase + { + private string _foo; + private Class2 _class2; + + public string Foo + { + get { return _foo; } + set + { + _foo = value; + RaisePropertyChanged(nameof(Foo)); + } + } + + public Class2 Class2 + { + get { return _class2; } + set + { + _class2 = value; + RaisePropertyChanged(nameof(Class2)); + } + } + } + + private class Class2 : NotifyingBase + { + private string _bar; + + public string Bar + { + get { return _bar; } + set + { + _bar = value; + RaisePropertyChanged(nameof(Bar)); + } + } + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs new file mode 100644 index 0000000000..ba8cfc57f7 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs @@ -0,0 +1,38 @@ +// 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.ComponentModel; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class NotifyingBase : INotifyPropertyChanged + { + private PropertyChangedEventHandler _propertyChanged; + + public event PropertyChangedEventHandler PropertyChanged + { + add + { + _propertyChanged += value; + ++SubscriptionCount; + } + + remove + { + _propertyChanged -= value; + --SubscriptionCount; + } + } + + public int SubscriptionCount + { + get; + private set; + } + + protected void RaisePropertyChanged(string propertyName) + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj new file mode 100644 index 0000000000..f1c7abc376 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -0,0 +1,103 @@ + + + + + + + Debug + AnyCPU + {8EF392D5-1416-45AA-9956-7CBBC3229E8A} + Library + Properties + Perspex.Markup.UnitTests + Perspex.Markup.UnitTests + v4.6 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll + True + + + + + + + + + + + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/tests/Perspex.Markup.UnitTests/Properties/AssemblyInfo.cs b/tests/Perspex.Markup.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..34123fa8b5 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Markup.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Markup.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8ef392d5-1416-45aa-9956-7cbbc3229e8a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Perspex.Markup.UnitTests/packages.config b/tests/Perspex.Markup.UnitTests/packages.config new file mode 100644 index 0000000000..0d1a6f02e7 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/packages.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file