|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Test;
using Microsoft.CodeAnalysis.Editor.Wpf;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.NavigateTo;
using Microsoft.CodeAnalysis.Remote.Testing;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Language.NavigateTo.Interfaces;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.PatternMatching;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Test.EditorUtilities.NavigateTo;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo
{
[UseExportProvider]
public abstract partial class AbstractNavigateToTests
{
protected static readonly TestComposition DefaultComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService));
protected static readonly TestComposition FirstVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocIsVisibleDocumentTrackingService.Factory));
protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory));
protected INavigateToItemProvider _provider;
protected NavigateToTestAggregator _aggregator;
internal static readonly PatternMatch s_emptyExactPatternMatch = new PatternMatch(PatternMatchKind.Exact, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyPrefixPatternMatch = new PatternMatch(PatternMatchKind.Prefix, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptySubstringPatternMatch = new PatternMatch(PatternMatchKind.Substring, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseExactPatternMatch = new PatternMatch(PatternMatchKind.CamelCaseExact, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCasePrefixPatternMatch = new PatternMatch(PatternMatchKind.CamelCasePrefix, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseNonContiguousPrefixPatternMatch = new PatternMatch(PatternMatchKind.CamelCaseNonContiguousPrefix, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseSubstringPatternMatch = new PatternMatch(PatternMatchKind.CamelCaseSubstring, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseNonContiguousSubstringPatternMatch = new PatternMatch(PatternMatchKind.CamelCaseNonContiguousSubstring, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyFuzzyPatternMatch = new PatternMatch(PatternMatchKind.Fuzzy, true, true, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyExactPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.Exact, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyPrefixPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.Prefix, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptySubstringPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.Substring, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseExactPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.CamelCaseExact, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCasePrefixPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.CamelCasePrefix, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseNonContiguousPrefixPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.CamelCaseNonContiguousPrefix, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseSubstringPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.CamelCaseSubstring, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyCamelCaseNonContiguousSubstringPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.CamelCaseNonContiguousSubstring, true, false, ImmutableArray<Span>.Empty);
internal static readonly PatternMatch s_emptyFuzzyPatternMatch_NotCaseSensitive = new PatternMatch(PatternMatchKind.Fuzzy, true, false, ImmutableArray<Span>.Empty);
protected abstract EditorTestWorkspace CreateWorkspace(string content, TestComposition composition);
protected abstract string Language { get; }
public enum Composition
{
Default,
FirstVisible,
FirstActiveAndVisible,
}
private protected static NavigateToItemProvider CreateProvider(EditorTestWorkspace workspace)
=> new(
workspace,
workspace.GetService<IThreadingContext>(),
workspace.GetService<IUIThreadOperationExecutor>(),
AsynchronousOperationListenerProvider.NullListener);
protected async Task TestAsync(TestHost testHost, Composition composition, string content, Func<EditorTestWorkspace, Task> body)
{
var testComposition = composition switch
{
Composition.Default => DefaultComposition,
Composition.FirstVisible => FirstVisibleComposition,
Composition.FirstActiveAndVisible => FirstActiveAndVisibleComposition,
_ => throw ExceptionUtilities.UnexpectedValue(composition),
};
await TestAsync(content, body, testHost, testComposition);
}
protected async Task TestAsync(TestHost testHost, Composition composition, XElement content, Func<EditorTestWorkspace, Task> body)
{
var testComposition = composition switch
{
Composition.Default => DefaultComposition,
Composition.FirstVisible => FirstVisibleComposition,
Composition.FirstActiveAndVisible => FirstActiveAndVisibleComposition,
_ => throw ExceptionUtilities.UnexpectedValue(composition),
};
await TestAsync(content, body, testHost, testComposition);
}
private async Task TestAsync(
string content, Func<EditorTestWorkspace, Task> body, TestHost testHost,
TestComposition composition)
{
using var workspace = CreateWorkspace(content, testHost, composition);
await body(workspace);
}
protected async Task TestAsync(
XElement content, Func<EditorTestWorkspace, Task> body, TestHost testHost,
TestComposition composition)
{
using var workspace = CreateWorkspace(content, testHost, composition);
await body(workspace);
}
private protected EditorTestWorkspace CreateWorkspace(
XElement workspaceElement,
TestHost testHost,
TestComposition composition)
{
composition = composition.WithTestHostParts(testHost);
var workspace = EditorTestWorkspace.Create(workspaceElement, composition: composition);
InitializeWorkspace(workspace);
return workspace;
}
private protected EditorTestWorkspace CreateWorkspace(
string content,
TestHost testHost,
TestComposition composition)
{
composition = composition.WithTestHostParts(testHost);
var workspace = CreateWorkspace(content, composition);
InitializeWorkspace(workspace);
return workspace;
}
internal void InitializeWorkspace(EditorTestWorkspace workspace)
{
_provider = new NavigateToItemProvider(
workspace,
workspace.GetService<IThreadingContext>(),
workspace.GetService<IUIThreadOperationExecutor>(),
AsynchronousOperationListenerProvider.NullListener);
_aggregator = new NavigateToTestAggregator(_provider);
}
protected static void VerifyNavigateToResultItems(
List<NavigateToItem> expecteditems, IEnumerable<NavigateToItem> items)
{
expecteditems = [.. expecteditems.OrderBy(i => i.Name)];
items = items.OrderBy(i => i.Name).ToList();
Assert.Equal(expecteditems.Count(), items.Count());
for (var i = 0; i < expecteditems.Count; i++)
{
var expectedItem = expecteditems[i];
var actualItem = items.ElementAt(i);
Assert.Equal(expectedItem.Name, actualItem.Name);
Assert.True(expectedItem.PatternMatch.Kind == actualItem.PatternMatch.Kind, string.Format("pattern: {0} expected: {1} actual: {2}", expectedItem.Name, expectedItem.PatternMatch.Kind, actualItem.PatternMatch.Kind));
Assert.True(expectedItem.PatternMatch.IsCaseSensitive == actualItem.PatternMatch.IsCaseSensitive, string.Format("pattern: {0} expected: {1} actual: {2}", expectedItem.Name, expectedItem.PatternMatch.IsCaseSensitive, actualItem.PatternMatch.IsCaseSensitive));
Assert.Equal(expectedItem.Language, actualItem.Language);
Assert.Equal(expectedItem.Kind, actualItem.Kind);
if (!string.IsNullOrEmpty(expectedItem.SecondarySort))
{
Assert.Contains(expectedItem.SecondarySort, actualItem.SecondarySort, StringComparison.Ordinal);
}
}
}
internal void VerifyNavigateToResultItem(
NavigateToItem result, string name, string displayMarkup,
PatternMatchKind matchKind, string navigateToItemKind,
Glyph glyph, string additionalInfo = null)
{
// Verify symbol information
Assert.Equal(name, result.Name);
Assert.Equal(matchKind, result.PatternMatch.Kind);
Assert.Equal(this.Language, result.Language);
Assert.Equal(navigateToItemKind, result.Kind);
MarkupTestFile.GetSpans(displayMarkup, out displayMarkup, out var expectedDisplayNameSpans);
var itemDisplay = (NavigateToItemDisplay)result.DisplayFactory.CreateItemDisplay(result);
Assert.Equal(itemDisplay.GlyphMoniker, glyph.GetImageMoniker());
Assert.Equal(displayMarkup, itemDisplay.Name);
Assert.Equal<TextSpan>(
expectedDisplayNameSpans,
itemDisplay.GetNameMatchRuns("").Select(s => s.ToTextSpan()).ToImmutableArray());
if (additionalInfo != null)
{
Assert.Equal(additionalInfo, itemDisplay.AdditionalInformation);
}
}
internal static BitmapSource CreateIconBitmapSource()
{
var stride = PixelFormats.Bgr32.BitsPerPixel / 8 * 16;
return BitmapSource.Create(16, 16, 96, 96, PixelFormats.Bgr32, null, new byte[16 * stride], stride);
}
// For ordering of NavigateToItems, see
// http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.language.navigateto.interfaces.navigatetoitem.aspx
protected static int CompareNavigateToItems(NavigateToItem a, NavigateToItem b)
=> ComparerWithState.CompareTo(a, b, s_comparisonComponents);
private static readonly ImmutableArray<Func<NavigateToItem, IComparable>> s_comparisonComponents =
ImmutableArray.Create<Func<NavigateToItem, IComparable>>(
item => (int)item.PatternMatch.Kind,
item => item.Name,
item => item.Kind,
item => item.SecondarySort);
private class FirstDocIsVisibleDocumentTrackingService : IDocumentTrackingService
{
private readonly Workspace _workspace;
[Obsolete(MefConstruction.FactoryMethodMessage, error: true)]
private FirstDocIsVisibleDocumentTrackingService(Workspace workspace)
=> _workspace = workspace;
public event EventHandler<DocumentId> ActiveDocumentChanged { add { } remove { } }
public DocumentId TryGetActiveDocument()
=> null;
public ImmutableArray<DocumentId> GetVisibleDocuments()
=> ImmutableArray.Create(_workspace.CurrentSolution.Projects.First().DocumentIds.First());
[ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable]
public class Factory : IWorkspaceServiceFactory
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public Factory()
{
}
[Obsolete(MefConstruction.FactoryMethodMessage, error: true)]
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
=> new FirstDocIsVisibleDocumentTrackingService(workspaceServices.Workspace);
}
}
[ExportWorkspaceService(typeof(IWorkspaceNavigateToSearcherHostService))]
[PartNotDiscoverable, Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
protected sealed class TestWorkspaceNavigateToSearchHostService() : IWorkspaceNavigateToSearcherHostService
{
public ValueTask<bool> IsFullyLoadedAsync(CancellationToken cancellationToken)
=> new(true);
}
}
}
|