Skip to content

Commit

Permalink
Issue 36/vs 2019 (#39)
Browse files Browse the repository at this point in the history
* VS2019 added to supported version range

Resolves #36

* Improved sanity checks to simplify debugging migration to async package

#36

* Improved sanity checks to simplify debugging migration to async package

#36

* Improved sanity checks to simplify debugging migration to async package

#36

* Improved sanity checks to simplify debugging migration to async package

#36

* Improved sanity checks to simplify debugging migration to async package

#36

* Cleanup

* Improved sanity checks to simplify debugging migration to async package

#36

* Improved sanity checks to simplify debugging migration to async package

#36

* Improved sanity checks to simplify debugging migration to async package

#36

* Begun backward incompatible(!) upgrade of VS references to support VS 2015+ async package load

#36

* Working XPath in statusbar

#36

* Moved all suitable extension initialization logic out of the VS package class

#36

* Cleanup

* Search now working with async package

#36

* Cleanup

* Reworked command visibility

Commands are now hidden until package is loaded or visibility constraints are satisfied.

#36

* Failing attempts at using VisibilityConstraints to control command visibility

#36

* Fixed autoload behavior of package and commands

#36

* Removed unused visibility constraints

#36

* Statusbar related code moved to separate namespace for clarity

* Changed code to allow for "early" configuration initialization required by the StatusbarAdapter, while allowing IConfiguration to be initialized "late" by async package load

#36

* Fixed constructor usage

* Cleanup

* Test marked as ignored for now

* Cleanup

* Configuration related code moved to separate namespace for clarity

* XPathSetting moved to Configuration

* Begun implementing configuration end-to-end tests

* Further work in progress

* Improved Tools and Options menu interaction

* Fixed options menu item text

* Working automation model

* Running/failing tests

* Working tests, failing due to VS quotation mark completion when inserting/"typing" XML

* Working tests

* Begun refactoring XML file creation to use temporary files

Working "against" VIsual Studio editor functionality is too cumbersome and prone to error when taking additional extensions into account.

* Begun implementing "Open File" interactions

* Working "Open File" interaction logic

* Begun enablling tests

* Solved directory name selection issue

* Cleanup

* Automation model for statusbar added

* Tests successful

* Reduced redundant setup steps

* Cleanup

* Reduced duplicate test setup

* Cleanup

* Reduced duplicate setup steps

* Fixed slow context menu entry lookup

* Version number and release notes updated

#36

* Supported VS version range updated to exclude VS 2013 and earlier
  • Loading branch information
uli-weltersbach authored Mar 8, 2019
1 parent 3998ac0 commit 0b22b99
Show file tree
Hide file tree
Showing 44 changed files with 622 additions and 357 deletions.
Original file line number Diff line number Diff line change
@@ -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 = "<a><b id='hello'><c/></b><b id='world'><c/></b></a>";
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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*")]
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,29 @@
<HintPath>..\packages\VSSDK.GraphModel.11.0.4\lib\net45\Microsoft.VisualStudio.GraphModel.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Imaging, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\VSSDK.OLE.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.OLE.Interop.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\VSSDK.Shell.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.11.0.dll</HintPath>
<Private>True</Private>
<Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\VSSDK.Shell.Immutable.10.10.0.4\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\VSSDK.Shell.Immutable.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\VSSDK.Shell.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<EmbedInteropTypes>False</EmbedInteropTypes>
Expand All @@ -91,12 +95,10 @@
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\VSSDK.Shell.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\VSSDK.Shell.Interop.9.9.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Text.Data, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\VSSDK.Text.11.0.4\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath>
Expand All @@ -111,12 +113,19 @@
<HintPath>..\packages\VSSDK.Text.11.0.4\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\VSSDK.TextManager.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\VSSDK.TextManager.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath>
</Reference>
<Reference Include="NSubstitute, Version=3.1.0.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL">
<HintPath>..\packages\NSubstitute.3.1.0\lib\net46\NSubstitute.dll</HintPath>
Expand Down Expand Up @@ -154,8 +163,11 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="VisualStudioIntegration\StatusbarAdapterTests.cs" />
<Compile Include="VisualStudioIntegration\XPathToolsPackageTests.cs" />
<Compile Include="Configuration\XPathToolsDialogPageAutomationModel.cs" />
<Compile Include="Statusbar\StatusbarAdapterTests.cs" />
<Compile Include="Configuration\ConfigurationTests.cs" />
<Compile Include="Statusbar\StatusbarAutomationModel.cs" />
<Compile Include="TestCategory.cs" />
<Compile Include="Writers\XPathWriterFactoryTests.cs" />
<Compile Include="XmlStringExtensions.cs" />
<Compile Include="VisualStudioIntegration\CaretPositionLineInfoTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = "<e1><e2><e3 /></e2></e1>";
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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
7 changes: 7 additions & 0 deletions ReasonCodeExample.XPathTools.Tests/TestCategory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ReasonCodeExample.XPathTools.Tests
{
internal static class TestCategory
{
public const string Integration = "Integration";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@ internal static class AutomationElementExtensions
public static AutomationElement FindDescendantByType<T>(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);
Expand All @@ -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)
Expand Down Expand Up @@ -75,4 +75,4 @@ public static string GetText(this AutomationElement element)
}
}
}
}
}
Loading

0 comments on commit 0b22b99

Please sign in to comment.