diff --git a/ReasonCodeExample.XPathTools.Tests/Configuration/ConfigurationTests.cs b/ReasonCodeExample.XPathTools.Tests/Configuration/ConfigurationTests.cs new file mode 100644 index 0000000..7791de8 --- /dev/null +++ b/ReasonCodeExample.XPathTools.Tests/Configuration/ConfigurationTests.cs @@ -0,0 +1,47 @@ +using System.Windows.Forms; +using NUnit.Framework; +using ReasonCodeExample.XPathTools.Tests.Statusbar; +using ReasonCodeExample.XPathTools.Tests.VisualStudioIntegration; +using ReasonCodeExample.XPathTools.VisualStudioIntegration; + +namespace ReasonCodeExample.XPathTools.Tests.Configuration +{ + [TestFixture] + [Category(TestCategory.Integration)] + public class ConfigurationTests + { + private readonly VisualStudioExperimentalInstance _visualStudio = new VisualStudioExperimentalInstance(); + + [OneTimeSetUp] + public void StartVisualStudio() + { + _visualStudio.ReStart(); + var xml = ""; + var xmlElementIndex = 41; + _visualStudio.OpenXmlFile(xml, xmlElementIndex); + } + + [OneTimeTearDown] + public void StopVisualStudio() + { + _visualStudio.Stop(); + } + + [TestCase(XPathFormat.Generic, "/a/b/c")] + [TestCase(XPathFormat.Absolute, "/a[1]/b[2]/c[1]")] + [TestCase(XPathFormat.Distinct, "/a/b[@id='world']/c")] + public void StatusbarXPathFormatChangesWhenConfigurationIsChanged(XPathFormat xpathFormat, string expectedXPath) + { + // Arrange + var configuration = new XPathToolsDialogPageAutomationModel(_visualStudio); + + // Act + configuration.SetStatusbarXPathFormat(xpathFormat); + SendKeys.SendWait("{LEFT}{RIGHT}"); // Move the caret to trigger a statusbar update + + // Assert + var statusbar = new StatusbarAutomationModel(_visualStudio.MainWindow); + Assert.That(statusbar.GetText(), Is.EqualTo(expectedXPath)); + } + } +} diff --git a/ReasonCodeExample.XPathTools.Tests/Configuration/XPathToolsDialogPageAutomationModel.cs b/ReasonCodeExample.XPathTools.Tests/Configuration/XPathToolsDialogPageAutomationModel.cs new file mode 100644 index 0000000..e6a1943 --- /dev/null +++ b/ReasonCodeExample.XPathTools.Tests/Configuration/XPathToolsDialogPageAutomationModel.cs @@ -0,0 +1,95 @@ +using ReasonCodeExample.XPathTools.Tests.VisualStudioIntegration; +using ReasonCodeExample.XPathTools.VisualStudioIntegration; +using System.Windows.Automation; +using System.Windows.Forms; + +namespace ReasonCodeExample.XPathTools.Tests.Configuration +{ + internal class XPathToolsDialogPageAutomationModel + { + private readonly VisualStudioExperimentalInstance _visualStudio; + private readonly AutomationElement _mainWindow; + + public XPathToolsDialogPageAutomationModel(VisualStudioExperimentalInstance visualStudio) + { + _visualStudio = visualStudio; + _mainWindow = visualStudio.MainWindow; + } + + private bool IsOpen + { + get + { + return OptionsDialog != null; + } + } + + private AutomationElement OptionsDialog + { + get + { + return _mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Options", PropertyConditionFlags.IgnoreCase)); + } + } + + private void Open() + { + var toolsMenu = OpenToolsMenu(); + + var optionsDialog = OpenOptionsDialog(toolsMenu); + + SetXPathToolsSettingsFocus(optionsDialog); + } + + private AutomationElement OpenToolsMenu() + { + var toolsMenu = _visualStudio.FindMenuItem("Tools"); + toolsMenu.LeftClick(); + return toolsMenu; + } + + private AutomationElement OpenOptionsDialog(AutomationElement toolsMenu) + { + var optionsMenuEntry = _visualStudio.FindMenuItem("Options...", toolsMenu); + optionsMenuEntry.LeftClick(); + + var optionsDialog = _mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Options")); + return optionsDialog; + } + + private void SetXPathToolsSettingsFocus(AutomationElement optionsDialog) + { + var xpathToolsSettings = optionsDialog.FindDescendantByText("XPath Tools"); + xpathToolsSettings.SetFocus(); + xpathToolsSettings.LeftClick(); + var propertiesWindow = optionsDialog.FindDescendant(new PropertyCondition(AutomationElement.NameProperty, "Properties Window", PropertyConditionFlags.IgnoreCase)); + propertiesWindow.LeftClick(); + } + + private void Close() + { + if (IsOpen) + { + OptionsDialog.FindDescendantByText("OK").LeftClick(); + } + } + + public void SetStatusbarXPathFormat(XPathFormat format) + { + // Ensure options dialog is closed before starting interaction sequence + Close(); + + // Open the XPath options dialog page + Open(); + + // Move to the last setting - this assumes that the XPath Tools settings page has focus and has 6 settings + SendKeys.SendWait("{DOWN 6}"); + + // Select the desired format - requires all formats to start with a different letter! + var firstLetter = format.ToString()[0].ToString(); + SendKeys.SendWait(firstLetter); + + Close(); + } + } +} diff --git a/ReasonCodeExample.XPathTools.Tests/Properties/AssemblyInfo.cs b/ReasonCodeExample.XPathTools.Tests/Properties/AssemblyInfo.cs index 59f01a6..4e130f4 100644 --- a/ReasonCodeExample.XPathTools.Tests/Properties/AssemblyInfo.cs +++ b/ReasonCodeExample.XPathTools.Tests/Properties/AssemblyInfo.cs @@ -5,4 +5,4 @@ [assembly: AssemblyCompany("Reason→Code→Example (http://reasoncodeexample.com)")] [assembly: AssemblyProduct("ReasonCodeExample.XPathTools.Tests")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("5.2.2.*")] +[assembly: AssemblyVersion("6.0.0.*")] diff --git a/ReasonCodeExample.XPathTools.Tests/ReasonCodeExample.XPathTools.Tests.csproj b/ReasonCodeExample.XPathTools.Tests/ReasonCodeExample.XPathTools.Tests.csproj index ef1d859..bedaf88 100644 --- a/ReasonCodeExample.XPathTools.Tests/ReasonCodeExample.XPathTools.Tests.csproj +++ b/ReasonCodeExample.XPathTools.Tests/ReasonCodeExample.XPathTools.Tests.csproj @@ -60,25 +60,29 @@ ..\packages\VSSDK.GraphModel.11.0.4\lib\net45\Microsoft.VisualStudio.GraphModel.dll True + + ..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll + - ..\packages\VSSDK.OLE.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.OLE.Interop.dll - True + ..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll - - ..\packages\VSSDK.Shell.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.11.0.dll - True + + ..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll - ..\packages\VSSDK.Shell.Immutable.10.10.0.4\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll - True + ..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll - ..\packages\VSSDK.Shell.Immutable.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll - True + ..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll + + + ..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll + + + ..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll - ..\packages\VSSDK.Shell.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.dll - True + ..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll False @@ -91,12 +95,10 @@ True - ..\packages\VSSDK.Shell.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.8.0.dll - True + ..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll - ..\packages\VSSDK.Shell.Interop.9.9.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.9.0.dll - True + ..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll ..\packages\VSSDK.Text.11.0.4\lib\net45\Microsoft.VisualStudio.Text.Data.dll @@ -111,12 +113,19 @@ ..\packages\VSSDK.Text.11.0.4\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - ..\packages\VSSDK.TextManager.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.dll - True + ..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll - ..\packages\VSSDK.TextManager.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.8.0.dll - True + ..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + + + ..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll + + + ..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll + + + ..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll ..\packages\NSubstitute.3.1.0\lib\net46\NSubstitute.dll @@ -154,8 +163,11 @@ - - + + + + + diff --git a/ReasonCodeExample.XPathTools.Tests/Statusbar/StatusbarAdapterTests.cs b/ReasonCodeExample.XPathTools.Tests/Statusbar/StatusbarAdapterTests.cs new file mode 100644 index 0000000..e797fe9 --- /dev/null +++ b/ReasonCodeExample.XPathTools.Tests/Statusbar/StatusbarAdapterTests.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; +using ReasonCodeExample.XPathTools.Tests.VisualStudioIntegration; + +namespace ReasonCodeExample.XPathTools.Tests.Statusbar +{ + [TestFixture] + [Category(TestCategory.Integration)] + public class StatusbarAdapterTests + { + private readonly VisualStudioExperimentalInstance _visualStudio = new VisualStudioExperimentalInstance(); + + [OneTimeSetUp] + public void StartVisualStudio() + { + _visualStudio.ReStart(); + } + + [OneTimeTearDown] + public void StopVisualStudio() + { + _visualStudio.Stop(); + } + + [Test] + public void StatusbarShowsXPath() + { + // Arrange + var xml = ""; + var caretPosition = 11; + var expectedXPath = "/e1/e2/e3"; + _visualStudio.OpenXmlFile(xml, caretPosition); + + // Act + var statusbar = new StatusbarAutomationModel(_visualStudio.MainWindow); + + // Assert + Assert.That(statusbar.GetText(), Is.EqualTo(expectedXPath)); + } + } +} diff --git a/ReasonCodeExample.XPathTools.Tests/Statusbar/StatusbarAutomationModel.cs b/ReasonCodeExample.XPathTools.Tests/Statusbar/StatusbarAutomationModel.cs new file mode 100644 index 0000000..5434471 --- /dev/null +++ b/ReasonCodeExample.XPathTools.Tests/Statusbar/StatusbarAutomationModel.cs @@ -0,0 +1,21 @@ +using System.Windows.Automation; +using ReasonCodeExample.XPathTools.Tests.VisualStudioIntegration; + +namespace ReasonCodeExample.XPathTools.Tests.Statusbar +{ + internal class StatusbarAutomationModel + { + private readonly AutomationElement _mainWindow; + + public StatusbarAutomationModel(AutomationElement mainWindow) + { + _mainWindow = mainWindow; + } + + public string GetText() + { + var liveTextBlock = _mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "LiveTextBlock")); + return liveTextBlock.GetText(); + } + } +} diff --git a/ReasonCodeExample.XPathTools.Tests/TestCategory.cs b/ReasonCodeExample.XPathTools.Tests/TestCategory.cs new file mode 100644 index 0000000..6c327b5 --- /dev/null +++ b/ReasonCodeExample.XPathTools.Tests/TestCategory.cs @@ -0,0 +1,7 @@ +namespace ReasonCodeExample.XPathTools.Tests +{ + internal static class TestCategory + { + public const string Integration = "Integration"; + } +} diff --git a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/AutomationElementExtensions.cs b/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/AutomationElementExtensions.cs index 25acc17..9dc043d 100644 --- a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/AutomationElementExtensions.cs +++ b/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/AutomationElementExtensions.cs @@ -12,28 +12,28 @@ internal static class AutomationElementExtensions public static AutomationElement FindDescendantByType(this AutomationElement ancestor, int timeoutInSeconds = DefaultTimeoutInSeconds) where T : Control { var propertyCondition = new PropertyCondition(AutomationElement.ClassNameProperty, typeof(T).Name, PropertyConditionFlags.IgnoreCase); - return FindDescendant(ancestor, timeoutInSeconds, propertyCondition); + return FindDescendant(ancestor, propertyCondition, timeoutInSeconds); } public static AutomationElement FindDescendantByAutomationId(this AutomationElement ancestor, string automationId, int timeoutInSeconds = DefaultTimeoutInSeconds) { var propertyCondition = new PropertyCondition(AutomationElement.AutomationIdProperty, automationId, PropertyConditionFlags.IgnoreCase); - return FindDescendant(ancestor, timeoutInSeconds, propertyCondition); + return FindDescendant(ancestor, propertyCondition, timeoutInSeconds); } public static AutomationElement FindDescendantByText(this AutomationElement ancestor, string descendantElementText, int timeoutInSeconds = DefaultTimeoutInSeconds) { var propertyCondition = new PropertyCondition(AutomationElement.NameProperty, descendantElementText, PropertyConditionFlags.IgnoreCase); - return FindDescendant(ancestor, timeoutInSeconds, propertyCondition); + return FindDescendant(ancestor, propertyCondition, timeoutInSeconds); } - private static AutomationElement FindDescendant(AutomationElement ancestor, int timeoutInSeconds, PropertyCondition propertyCondition) + public static AutomationElement FindDescendant(this AutomationElement ancestor, Condition condition, int timeoutInSeconds = DefaultTimeoutInSeconds) { var timeout = DateTime.UtcNow.AddSeconds(timeoutInSeconds); var retryInterval = TimeSpan.FromSeconds(1); while(DateTime.UtcNow < timeout) { - var match = ancestor.FindFirst(TreeScope.Descendants, propertyCondition); + var match = ancestor.FindFirst(TreeScope.Descendants, condition); if(match == null) { Thread.Sleep(retryInterval); @@ -43,7 +43,7 @@ private static AutomationElement FindDescendant(AutomationElement ancestor, int return match; } } - throw new TimeoutException($"Element \"{propertyCondition.Value}\" wasn't found within {timeoutInSeconds} seconds."); + throw new TimeoutException($"Element \"{condition}\" wasn't found within {timeoutInSeconds} seconds."); } public static AutomationElement LeftClick(this AutomationElement element) @@ -75,4 +75,4 @@ public static string GetText(this AutomationElement element) } } } -} \ No newline at end of file +} diff --git a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/StatusbarAdapterTests.cs b/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/StatusbarAdapterTests.cs deleted file mode 100644 index ee9110d..0000000 --- a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/StatusbarAdapterTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -using NUnit.Framework; - -namespace ReasonCodeExample.XPathTools.Tests.VisualStudioIntegration -{ - [TestFixture] - [Category("Integration")] - public class StatusbarAdapterTests - { - private readonly VisualStudioExperimentalInstance _instance = new VisualStudioExperimentalInstance(); - - [OneTimeSetUp] - public void StartVisualStudio() - { - _instance.ReStart(); - } - - [OneTimeTearDown] - public void StopVisualStudio() - { - _instance.Stop(); - } - - [Test] - public void StatusbarShowsXPath() - { - // Arrange - var xml = ""; - var caretPosition = 11; - var expectedXPath = "/e1/e2/e3"; - _instance.OpenXmlFile(xml, caretPosition); - - // Act - var statusbar = _instance.MainWindow.FindDescendantByText(expectedXPath); - - // Assert - Assert.That(statusbar, Is.Not.Null); - } - } -} diff --git a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/VisualStudioExperimentalInstance.cs b/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/VisualStudioExperimentalInstance.cs index f6eee5d..276bcb1 100644 --- a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/VisualStudioExperimentalInstance.cs +++ b/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/VisualStudioExperimentalInstance.cs @@ -20,15 +20,17 @@ public AutomationElement MainWindow get { _process = FindExperimentalInstance(); - if(_process == null) + if (_process == null) { return null; } - if(_mainWindow == null) + + if (_mainWindow == null) { var processIdCondition = new PropertyCondition(AutomationElement.ProcessIdProperty, _process.Id); _mainWindow = AutomationElement.RootElement.FindFirst(TreeScope.Descendants, processIdCondition); } + return _mainWindow; } } @@ -41,12 +43,12 @@ private Process FindExperimentalInstance() public void ReStart() { Stop(); - var programsFolder = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); var executablePath = FindLatestVisualStudioUsingVswhere(); - if(!executablePath.Exists) + if (!executablePath.Exists) { throw new FileNotFoundException($"Didn't find Visual Studio executable at \"{executablePath}\"."); } + // The VisualStudio process spawns a new process with a different ID. Process.Start(new ProcessStartInfo(executablePath.FullName, "/RootSuffix Exp /ResetSkipPkgs")); WaitUntillStarted(TimeSpan.FromMinutes(3)); @@ -55,14 +57,16 @@ public void ReStart() public void Stop() { var process = FindExperimentalInstance(); - if(process == null) + if (process == null) { return; } - if(process.HasExited) + + if (process.HasExited) { return; } + process.Kill(); } @@ -74,6 +78,7 @@ private FileInfo FindLatestVisualStudioUsingVswhere() { throw new FileNotFoundException($"Didn't find vswhere.exe at \"{vswhereExePath}\"."); } + Process process = new Process(); process.StartInfo.FileName = vswhereExePath.FullName; process.StartInfo.Arguments = "-latest"; @@ -88,24 +93,27 @@ private FileInfo FindLatestVisualStudioUsingVswhere() { throw new Exception($"Error calling vswhere.exe (\"{vswhereExePath}\"): {error}"); } + if (string.IsNullOrWhiteSpace(output)) { throw new Exception($"vswhere.exe (\"{vswhereExePath}\") output was null or empty."); } + var match = Regex.Match(output, "^productPath: (.*)$", RegexOptions.Multiline); if (match.Success) { return new FileInfo(match.Groups[1].Value.Trim()); } + throw new Exception($"vswhere.exe (\"{vswhereExePath}\") output didn't include product path: {output}"); } private void WaitUntillStarted(TimeSpan timeoutDuration) { var timeout = DateTime.UtcNow.Add(timeoutDuration); - while(DateTime.UtcNow < timeout) + while (DateTime.UtcNow < timeout) { - if(MainWindow == null) + if (MainWindow == null) { Thread.Sleep(TimeSpan.FromSeconds(3)); } @@ -114,59 +122,99 @@ private void WaitUntillStarted(TimeSpan timeoutDuration) return; } } + throw new TimeoutException($"Visual Studio wasn't started within {timeoutDuration.TotalSeconds} seconds."); } public void OpenXmlFile(string content, int? caretPosition) { - OpenNewFileDialog(); - OpenNewXmlFile(); - InsertContentIntoNewXmlFile(content); - if(caretPosition.HasValue) + var temporaryFile = CreateTemporaryFile(content); + OpenFilePickerDialog(); + OpenTemporaryFile(temporaryFile); + if (caretPosition.HasValue) { SetCaretPosition(caretPosition.Value); } } - private void OpenNewFileDialog() + private FileInfo CreateTemporaryFile(string content) { - MainWindow.FindDescendantByText("File").LeftClick(); - MainWindow.FindDescendantByText("New").LeftClick(); - MainWindow.FindDescendantByText("File...").LeftClick(); + var temporaryFile = new FileInfo(Path.GetTempFileName()); + temporaryFile.MoveTo(temporaryFile.FullName + ".xml"); + File.WriteAllText(temporaryFile.FullName, content); + return temporaryFile; } - private void OpenNewXmlFile() + private void OpenFilePickerDialog() { - MainWindow.FindDescendantByText("XML File").LeftClick(); - MainWindow.FindDescendantByText("Open").LeftClick(); + FindMenuItem("File").LeftClick(); + FindMenuItem("Open").LeftClick(); + FindMenuItem("File...").LeftClick(); } - private void InsertContentIntoNewXmlFile(string content) + public AutomationElement FindMenuItem(string menuItemText, AutomationElement ancestor = null) { - // Write content starting on a new line, after the XML declaration - SendKeys.SendWait("{End}"); - SendKeys.SendWait("{Enter}"); - SendKeys.SendWait(content); + if (ancestor == null) + { + ancestor = MainWindow; + } + var className = "MenuItem"; + var classNameCondition = new PropertyCondition(AutomationElement.ClassNameProperty, className, PropertyConditionFlags.IgnoreCase); + var nameCondition = new PropertyCondition(AutomationElement.NameProperty, menuItemText, PropertyConditionFlags.IgnoreCase); + var condition = new AndCondition(classNameCondition, nameCondition); + return ancestor.FindDescendant(condition); + } + + private void OpenTemporaryFile(FileInfo xmlFIle) + { + var openFileDialog = MainWindow.FindDescendant(new PropertyCondition(AutomationElement.NameProperty, "Open File")); + + var directoryPickerCondition = new PropertyCondition(AutomationElement.NameProperty, "All locations", PropertyConditionFlags.IgnoreCase); + var directoryPicker = openFileDialog.FindDescendant(directoryPickerCondition); + directoryPicker.LeftClick(); + SendKeys.SendWait(xmlFIle.DirectoryName); + SendKeys.SendWait("{ENTER}"); + + var filePickerCondition = new AndCondition( + new PropertyCondition(AutomationElement.AutomationIdProperty, "1148"), + new PropertyCondition(AutomationElement.ClassNameProperty, "Edit")); + var filePicker = openFileDialog.FindDescendant(filePickerCondition); + filePicker.LeftClick(); + SendKeys.SendWait(xmlFIle.Name); + + var openButtonCondition = new AndCondition( + new PropertyCondition(AutomationElement.NameProperty, "Open"), + new PropertyCondition(AutomationElement.ClassNameProperty, "Button")); + var openButton = openFileDialog.FindDescendant(openButtonCondition); + openButton.LeftClick(); } private void SetCaretPosition(int caretPosition) { // Go to the start of the line and move forward from there - SendKeys.SendWait("{Home}"); - SendKeys.SendWait("{Right " + caretPosition + "}"); + SendKeys.SendWait("{HOME}"); + SendKeys.SendWait("{RIGHT " + caretPosition + "}"); + } + + public AutomationElement ClickContextMenuEntry(string entryName) + { + var contextMenu = OpenContextMenu(); + contextMenu.FindDescendantByText(entryName).LeftClick(); + return contextMenu; } - public void ClickContextMenuEntry(string entryName) + private AutomationElement OpenContextMenu() { // Use "shift F10" shortcut to open context menu SendKeys.SendWait("+{F10}"); - MainWindow.FindDescendantByText(entryName).LeftClick(); + var contextMenu = MainWindow.FindDescendant(new PropertyCondition(AutomationElement.ClassNameProperty, "ContextMenu", PropertyConditionFlags.IgnoreCase)); + return contextMenu; } public IList GetContextMenuSubMenuCommands(string subMenuName, Regex commandName) { - ClickContextMenuEntry(subMenuName); - var descendants = MainWindow.FindAll(TreeScope.Descendants, new PropertyCondition(AutomationElement.ProcessIdProperty, _process.Id)); + var contextMenu = ClickContextMenuEntry(subMenuName); + var descendants = contextMenu.FindAll(TreeScope.Descendants, new PropertyCondition(AutomationElement.ProcessIdProperty, _process.Id)); return (from AutomationElement descendant in descendants where descendant.GetSupportedProperties().Contains(AutomationElement.NameProperty) let elementName = descendant.GetCurrentPropertyValue(AutomationElement.NameProperty) diff --git a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/XPathToolsPackageTests.cs b/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/XPathToolsPackageTests.cs deleted file mode 100644 index 4611489..0000000 --- a/ReasonCodeExample.XPathTools.Tests/VisualStudioIntegration/XPathToolsPackageTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.Design; -using Microsoft.VisualStudio.Shell.Interop; -using NSubstitute; -using NUnit.Framework; -using ReasonCodeExample.XPathTools.VisualStudioIntegration; - -namespace ReasonCodeExample.XPathTools.Tests.VisualStudioIntegration -{ - public class XPathToolsPackageTests - { - [TestCase(XPathFormat.Generic, "", 19, "/a/b[@id='hello']/c")] - [TestCase(XPathFormat.Absolute, "", 19, "/a[1][@id='world']/b[1]/c[1]")] - public void StatusbarXPathFormatChangesWhenConfigurationIsChanged(XPathFormat xpathFormat, string xml, int xmlElementIndex, string expectedXPath) - { - // Arrange - var serviceContainer = new ServiceContainer(); - - var package = new XPathToolsPackage(serviceContainer); - - // Set up the configuration to a default XPathFormat - var configuration = Substitute.For(); - configuration.StatusbarXPathFormat.ReturnsForAnyArgs(XPathFormat.Simplified); - // Define an XML attribute which should always be shown in the statusbar XPath - configuration.AlwaysDisplayedAttributes.ReturnsForAnyArgs(new List{new XPathSetting{AttributeName = "id"}}); - configuration.PreferredAttributeCandidates.ReturnsForAnyArgs(new List()); - - // Initialize all services, incl. the statusbar adapter - var menuCommandService = Substitute.For(); - var statusbar = Substitute.For(); - package.Initialize(configuration, menuCommandService, statusbar); - - // Set up the XML repo initialized by the package - var xmlRepository = serviceContainer.Get(); - xmlRepository.LoadXml(xml, null); - var selectedElement = xmlRepository.GetElement(xmlRepository.GetRootElement(), 1, xmlElementIndex); - xmlRepository.SetSelected(selectedElement); - - var statusbarAdapter = serviceContainer.Get(); - - // Act - configuration.StatusbarXPathFormat.ReturnsForAnyArgs(xpathFormat); - statusbarAdapter.SetText(null, null); - - // Assert - statusbar.Received().SetText(Arg.Is(expectedXPath)); - } - } -} \ No newline at end of file diff --git a/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchAutomationModel.cs b/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchAutomationModel.cs index 7c4ca92..fb535d3 100644 --- a/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchAutomationModel.cs +++ b/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchAutomationModel.cs @@ -47,7 +47,7 @@ public string SearchText public void Search(string xpath) { SearchText = xpath; - SendKeys.SendWait("{Enter}"); + SendKeys.SendWait("{ENTER}"); } } -} \ No newline at end of file +} diff --git a/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchTests.cs b/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchTests.cs index 7270767..34c7e04 100644 --- a/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchTests.cs +++ b/ReasonCodeExample.XPathTools.Tests/Workbench/XPathWorkbenchTests.cs @@ -1,39 +1,35 @@ -using System.Xml.Linq; -using NUnit.Framework; +using NUnit.Framework; using ReasonCodeExample.XPathTools.Tests.VisualStudioIntegration; using ReasonCodeExample.XPathTools.VisualStudioIntegration; -using System.Windows.Forms; namespace ReasonCodeExample.XPathTools.Tests.Workbench { [TestFixture] - [Category("Integration")] + [Category(TestCategory.Integration)] public class XPathWorkbenchTests { - private readonly VisualStudioExperimentalInstance _instance = new VisualStudioExperimentalInstance(); + private readonly VisualStudioExperimentalInstance _visualStudio = new VisualStudioExperimentalInstance(); [OneTimeSetUp] public void StartVisualStudio() { - _instance.ReStart(); + _visualStudio.ReStart(); + var xml = ""; + _visualStudio.OpenXmlFile(xml, null); + _visualStudio.ClickContextMenuEntry(PackageResources.ShowXPathWorkbenchCommandText); } [OneTimeTearDown] public void StopVisualStudio() { - _instance.Stop(); + _visualStudio.Stop(); } [Test] public void WorkbenchIsActivatedViaContextMenu() { - // Arrange - var xml = new XElement("xml"); - _instance.OpenXmlFile(xml.ToString(), 0); - // Act - _instance.ClickContextMenuEntry(PackageResources.ShowXPathWorkbenchCommandText); - var xpathWorkbench = new XPathWorkbenchAutomationModel(_instance.MainWindow); + var xpathWorkbench = new XPathWorkbenchAutomationModel(_visualStudio.MainWindow); // Assert Assert.That(xpathWorkbench.IsVisible, Is.True); @@ -42,13 +38,8 @@ public void WorkbenchIsActivatedViaContextMenu() [Test] public void WorkbenchRunsQueryEvenThoughNoNodeIsSelected() { - // Arrange - var xml = new XElement("xml"); - _instance.OpenXmlFile(xml.ToString(), 0); - // Act - _instance.ClickContextMenuEntry(PackageResources.ShowXPathWorkbenchCommandText); - var xpathWorkbench = new XPathWorkbenchAutomationModel(_instance.MainWindow); + var xpathWorkbench = new XPathWorkbenchAutomationModel(_visualStudio.MainWindow); xpathWorkbench.Search("§ invalid XPath §"); // Assert @@ -59,14 +50,11 @@ public void WorkbenchRunsQueryEvenThoughNoNodeIsSelected() public void WorkbenchShowsSearchResultCount() { // Arrange - var xml = new XElement("xml"); - _instance.OpenXmlFile(xml.ToString(SaveOptions.DisableFormatting), 0); - _instance.ClickContextMenuEntry(PackageResources.ShowXPathWorkbenchCommandText); - var xpathWorkbench = new XPathWorkbenchAutomationModel(_instance.MainWindow); + var xpathWorkbench = new XPathWorkbenchAutomationModel(_visualStudio.MainWindow); var expectedResultText = string.Format(PackageResources.SingleResultText, 1); // Act - xpathWorkbench.Search("/xml"); + xpathWorkbench.Search("//*[local-name()='assemblyBinding']"); // Assert Assert.That(xpathWorkbench.SearchResultText, Does.Contain(expectedResultText)); @@ -76,9 +64,7 @@ public void WorkbenchShowsSearchResultCount() public void WorkbenchHandlesXmlNamespaces() { // Arrange - EnterXmlWithNestedQuotationMarks(); - _instance.ClickContextMenuEntry(PackageResources.ShowXPathWorkbenchCommandText); - var xpathWorkbench = new XPathWorkbenchAutomationModel(_instance.MainWindow); + var xpathWorkbench = new XPathWorkbenchAutomationModel(_visualStudio.MainWindow); var expectedResultText = string.Format(PackageResources.SingleResultText, 1); // Act @@ -87,15 +73,5 @@ public void WorkbenchHandlesXmlNamespaces() // Assert Assert.That(xpathWorkbench.SearchResultText, Does.Contain(expectedResultText)); } - - private void EnterXmlWithNestedQuotationMarks() - { - var xmlPart1 = ""; - SendKeys.SendWait(xmlPart2); - SendKeys.SendWait("{HOME}"); - } } } diff --git a/ReasonCodeExample.XPathTools.Tests/Writers/AttributeFilterTests.cs b/ReasonCodeExample.XPathTools.Tests/Writers/AttributeFilterTests.cs index c27b670..9d19004 100644 --- a/ReasonCodeExample.XPathTools.Tests/Writers/AttributeFilterTests.cs +++ b/ReasonCodeExample.XPathTools.Tests/Writers/AttributeFilterTests.cs @@ -1,5 +1,6 @@ using System.Xml.Linq; using NUnit.Framework; +using ReasonCodeExample.XPathTools.Configuration; using ReasonCodeExample.XPathTools.Writers; namespace ReasonCodeExample.XPathTools.Tests.Writers diff --git a/ReasonCodeExample.XPathTools.Tests/Writers/CopyXPathCommandTests.cs b/ReasonCodeExample.XPathTools.Tests/Writers/CopyXPathCommandTests.cs index 70a2d7f..a16323c 100644 --- a/ReasonCodeExample.XPathTools.Tests/Writers/CopyXPathCommandTests.cs +++ b/ReasonCodeExample.XPathTools.Tests/Writers/CopyXPathCommandTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -11,7 +10,7 @@ namespace ReasonCodeExample.XPathTools.Tests.Writers { [TestFixture] - [Category("Integration")] + [Category(TestCategory.Integration)] [Apartment(ApartmentState.STA)] public class CopyXPathCommandTests { @@ -21,6 +20,11 @@ public class CopyXPathCommandTests public void StartVisualStudio() { _instance.ReStart(); + + // Arrange + var xml = ""; + var caretPosition = 2; + _instance.OpenXmlFile(xml, caretPosition); } [OneTimeTearDown] @@ -32,11 +36,6 @@ public void StopVisualStudio() [Test] public void XPathCommandsAreAvailable() { - // Arrange - var xml = ""; - var caretPosition = 2; - _instance.OpenXmlFile(xml, caretPosition); - // Act var matches = GetAvailableCopyXPathCommands(); @@ -48,10 +47,7 @@ public void XPathCommandsAreAvailable() public void GenericXPathIsCopiedToClipboard() { // Arrange - var xml = ""; - var caretPosition = 2; var expectedXPath = "/xml"; - _instance.OpenXmlFile(xml, caretPosition); var matches = GetAvailableCopyXPathCommands(); var copyGenericXPathCommand = matches.First(); diff --git a/ReasonCodeExample.XPathTools.Tests/Writers/DistinctAttributeFilterTests.cs b/ReasonCodeExample.XPathTools.Tests/Writers/DistinctAttributeFilterTests.cs index fc3c566..a88323c 100644 --- a/ReasonCodeExample.XPathTools.Tests/Writers/DistinctAttributeFilterTests.cs +++ b/ReasonCodeExample.XPathTools.Tests/Writers/DistinctAttributeFilterTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using ReasonCodeExample.XPathTools.Configuration; using ReasonCodeExample.XPathTools.Writers; namespace ReasonCodeExample.XPathTools.Tests.Writers diff --git a/ReasonCodeExample.XPathTools.Tests/Writers/XPathWriterFactoryTests.cs b/ReasonCodeExample.XPathTools.Tests/Writers/XPathWriterFactoryTests.cs index 859a9aa..502ec68 100644 --- a/ReasonCodeExample.XPathTools.Tests/Writers/XPathWriterFactoryTests.cs +++ b/ReasonCodeExample.XPathTools.Tests/Writers/XPathWriterFactoryTests.cs @@ -1,6 +1,7 @@ using System; using NSubstitute; using NUnit.Framework; +using ReasonCodeExample.XPathTools.Configuration; using ReasonCodeExample.XPathTools.VisualStudioIntegration; using ReasonCodeExample.XPathTools.Writers; @@ -16,7 +17,7 @@ public void CanCreateXPathWriter(XPathFormat format, Type expectedXPathWriterTyp { // Arrange var configuration = Substitute.For(); - var factory = new XPathWriterFactory(configuration); + var factory = new XPathWriterFactory(() => configuration); // Act var writer = factory.CreateForXPathFormat(format); @@ -25,4 +26,4 @@ public void CanCreateXPathWriter(XPathFormat format, Type expectedXPathWriterTyp Assert.That(writer, Is.Not.Null.And.TypeOf(expectedXPathWriterType)); } } -} \ No newline at end of file +} diff --git a/ReasonCodeExample.XPathTools.Tests/packages.config b/ReasonCodeExample.XPathTools.Tests/packages.config index 31c617e..090e706 100644 --- a/ReasonCodeExample.XPathTools.Tests/packages.config +++ b/ReasonCodeExample.XPathTools.Tests/packages.config @@ -1,6 +1,21 @@  + + + + + + + + + + + + + + + @@ -15,7 +30,6 @@ - diff --git a/ReasonCodeExample.XPathTools/ActiveDocument.cs b/ReasonCodeExample.XPathTools/ActiveDocument.cs index 65b1b8d..73c1a8c 100644 --- a/ReasonCodeExample.XPathTools/ActiveDocument.cs +++ b/ReasonCodeExample.XPathTools/ActiveDocument.cs @@ -11,6 +11,7 @@ public bool IsXmlDocument { get { + ThreadHelper.ThrowIfNotOnUIThread(); var dte = (DTE)Package.GetGlobalService(typeof(DTE)); var isXmlDocument = string.Equals(dte?.ActiveDocument?.Language, Constants.XmlContentTypeName, StringComparison.InvariantCultureIgnoreCase); return isXmlDocument; @@ -21,9 +22,10 @@ public string AbsolutePath { get { + ThreadHelper.ThrowIfNotOnUIThread(); var dte = (DTE)Package.GetGlobalService(typeof(DTE)); return dte?.ActiveDocument?.FullName; } } } -} \ No newline at end of file +} diff --git a/ReasonCodeExample.XPathTools/IConfiguration.cs b/ReasonCodeExample.XPathTools/Configuration/IConfiguration.cs similarity index 88% rename from ReasonCodeExample.XPathTools/IConfiguration.cs rename to ReasonCodeExample.XPathTools/Configuration/IConfiguration.cs index 4eed9e4..453877d 100644 --- a/ReasonCodeExample.XPathTools/IConfiguration.cs +++ b/ReasonCodeExample.XPathTools/Configuration/IConfiguration.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using ReasonCodeExample.XPathTools.VisualStudioIntegration; -namespace ReasonCodeExample.XPathTools +namespace ReasonCodeExample.XPathTools.Configuration { internal interface IConfiguration { diff --git a/ReasonCodeExample.XPathTools/VisualStudioIntegration/SerializableConverter.cs b/ReasonCodeExample.XPathTools/Configuration/SerializableConverter.cs similarity index 95% rename from ReasonCodeExample.XPathTools/VisualStudioIntegration/SerializableConverter.cs rename to ReasonCodeExample.XPathTools/Configuration/SerializableConverter.cs index e60ee88..64a8e7b 100644 --- a/ReasonCodeExample.XPathTools/VisualStudioIntegration/SerializableConverter.cs +++ b/ReasonCodeExample.XPathTools/Configuration/SerializableConverter.cs @@ -5,7 +5,7 @@ using System.Text; using System.Xml.Serialization; -namespace ReasonCodeExample.XPathTools.VisualStudioIntegration +namespace ReasonCodeExample.XPathTools.Configuration { public class SerializableConverter : TypeConverter { diff --git a/ReasonCodeExample.XPathTools/XPathSetting.cs b/ReasonCodeExample.XPathTools/Configuration/XPathSetting.cs similarity index 97% rename from ReasonCodeExample.XPathTools/XPathSetting.cs rename to ReasonCodeExample.XPathTools/Configuration/XPathSetting.cs index f58ffb4..266aef7 100644 --- a/ReasonCodeExample.XPathTools/XPathSetting.cs +++ b/ReasonCodeExample.XPathTools/Configuration/XPathSetting.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using System.Text; -namespace ReasonCodeExample.XPathTools +namespace ReasonCodeExample.XPathTools.Configuration { [Serializable] public class XPathSetting @@ -65,4 +65,4 @@ public override string ToString() return text.ToString(); } } -} \ No newline at end of file +} diff --git a/ReasonCodeExample.XPathTools/VisualStudioIntegration/XPathToolsDialogPage.cs b/ReasonCodeExample.XPathTools/Configuration/XPathToolsDialogPage.cs similarity index 97% rename from ReasonCodeExample.XPathTools/VisualStudioIntegration/XPathToolsDialogPage.cs rename to ReasonCodeExample.XPathTools/Configuration/XPathToolsDialogPage.cs index 7bdb711..95543f6 100644 --- a/ReasonCodeExample.XPathTools/VisualStudioIntegration/XPathToolsDialogPage.cs +++ b/ReasonCodeExample.XPathTools/Configuration/XPathToolsDialogPage.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing.Design; @@ -7,8 +6,9 @@ using System.Reflection; using Microsoft.VisualStudio.Shell; using Microsoft.Win32; +using ReasonCodeExample.XPathTools.VisualStudioIntegration; -namespace ReasonCodeExample.XPathTools.VisualStudioIntegration +namespace ReasonCodeExample.XPathTools.Configuration { internal class XPathToolsDialogPage : DialogPage, IConfiguration { diff --git a/ReasonCodeExample.XPathTools/Properties/AssemblyInfo.cs b/ReasonCodeExample.XPathTools/Properties/AssemblyInfo.cs index df04a93..099cff3 100644 --- a/ReasonCodeExample.XPathTools/Properties/AssemblyInfo.cs +++ b/ReasonCodeExample.XPathTools/Properties/AssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyCompany("Reason→Code→Example (http://reasoncodeexample.com)")] [assembly: AssemblyProduct("ReasonCodeExample.XPathTools")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("5.2.2.*")] +[assembly: AssemblyVersion("6.0.0.*")] [assembly: InternalsVisibleTo(InternalsVisibleTo.ReasonCodeExampleXPathToolsTests)] [assembly: InternalsVisibleTo(InternalsVisibleTo.DynamicProxyGenAssembly2)] [assembly: InternalsVisibleTo(InternalsVisibleTo.CastleCore)] diff --git a/ReasonCodeExample.XPathTools/ReasonCodeExample.XPathTools.csproj b/ReasonCodeExample.XPathTools/ReasonCodeExample.XPathTools.csproj index 9a3f8d9..02d9db3 100644 --- a/ReasonCodeExample.XPathTools/ReasonCodeExample.XPathTools.csproj +++ b/ReasonCodeExample.XPathTools/ReasonCodeExample.XPathTools.csproj @@ -26,6 +26,8 @@ true ..\ + + true @@ -85,14 +87,26 @@ ..\packages\VSSDK.CoreUtility.11.0.4\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + + ..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll + ..\packages\VSSDK.OLE.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.OLE.Interop.dll - - ..\packages\VSSDK.Shell.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.11.0.dll + + ..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll - ..\packages\VSSDK.Shell.Immutable.10.10.0.4\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll + ..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll + + + ..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll + + + ..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll + + + ..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll ..\packages\VSSDK.Shell.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.dll @@ -106,10 +120,10 @@ ..\packages\VSSDK.Shell.Interop.11.11.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.11.0.dll - ..\packages\VSSDK.Shell.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.8.0.dll + ..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll - ..\packages\VSSDK.Shell.Interop.9.9.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.9.0.dll + ..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll ..\packages\VSSDK.Text.11.0.4\lib\net45\Microsoft.VisualStudio.Text.Data.dll @@ -120,6 +134,18 @@ ..\packages\VSSDK.Text.11.0.4\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll + + ..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + + + ..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll + + + ..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll + + + ..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll + @@ -142,7 +168,7 @@ - + Component @@ -158,8 +184,8 @@ - - + + @@ -174,13 +200,13 @@ - + - + @@ -254,7 +280,10 @@ false - + + + + Designer @@ -264,6 +293,15 @@ + + + + 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}. + + + + +