// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Utilities; using Moq; using Xunit; using ITextBuffer = Microsoft.VisualStudio.Text.ITextBuffer; namespace Microsoft.VisualStudio.Editor.Razor { public class RazorDirectiveCompletionProviderTest { public RazorDirectiveCompletionProviderTest() { CompletionBroker = Mock.Of(broker => broker.IsCompletionSupported(It.IsAny()) == true); var razorBuffer = Mock.Of(buffer => buffer.ContentType == Mock.Of()); TextBufferProvider = Mock.Of(provider => provider.TryGetFromDocument(It.IsAny(), out razorBuffer) == true); CompletionFactsService = new DefaultRazorCompletionFactsService(); } private IAsyncCompletionBroker CompletionBroker { get; } private RazorTextBufferProvider TextBufferProvider { get; } private RazorCompletionFactsService CompletionFactsService { get; } [Fact] public async Task GetDescriptionAsync_AddsDirectiveDescriptionIfPropertyExists() { // Arrange var document = CreateDocument(); var expectedDescription = "The expected description"; var item = CompletionItem.Create("TestDirective") .WithProperties((new Dictionary() { [RazorDirectiveCompletionProvider.DescriptionKey] = expectedDescription, }).ToImmutableDictionary()); var codeDocumentProvider = new Mock(); var completionProvider = new RazorDirectiveCompletionProvider( new Lazy(() => codeDocumentProvider.Object), new Lazy(() => CompletionFactsService), CompletionBroker, TextBufferProvider); // Act var description = await completionProvider.GetDescriptionAsync(document, item, CancellationToken.None); // Assert var part = Assert.Single(description.TaggedParts); Assert.Equal(TextTags.Text, part.Tag); Assert.Equal(expectedDescription, part.Text); Assert.Equal(expectedDescription, description.Text); } [Fact] public async Task GetDescriptionAsync_DoesNotAddDescriptionWhenPropertyAbsent() { // Arrange var document = CreateDocument(); var item = CompletionItem.Create("TestDirective"); var codeDocumentProvider = new Mock(); var completionProvider = new RazorDirectiveCompletionProvider( new Lazy(() => codeDocumentProvider.Object), new Lazy(() => CompletionFactsService), CompletionBroker, TextBufferProvider); // Act var description = await completionProvider.GetDescriptionAsync(document, item, CancellationToken.None); // Assert Assert.Empty(description.TaggedParts); Assert.Equal(string.Empty, description.Text); } [Fact] public async Task ProvideCompletionAsync_DoesNotProvideCompletionsForNonRazorFiles() { // Arrange var codeDocumentProvider = new Mock(MockBehavior.Strict); var completionProvider = new FailOnGetCompletionsProvider( new Lazy(() => codeDocumentProvider.Object), CompletionBroker, TextBufferProvider); var document = CreateDocument(); document = document.WithFilePath("NotRazor.cs"); var context = CreateContext(1, completionProvider, document); // Act & Assert await completionProvider.ProvideCompletionsAsync(context); } [Fact] public async Task ProvideCompletionAsync_DoesNotProvideCompletionsForDocumentWithoutPath() { // Arrange Document document = null; TestWorkspace.Create(workspace => { var project = ProjectInfo .Create(ProjectId.CreateNewId(), VersionStamp.Default, "TestProject", "TestAssembly", LanguageNames.CSharp) .WithFilePath("/TestProject.csproj"); workspace.AddProject(project); var documentInfo = DocumentInfo.Create(DocumentId.CreateNewId(project.Id), "Test.cshtml"); document = workspace.AddDocument(documentInfo); }); var codeDocumentProvider = new Mock(MockBehavior.Strict); var completionProvider = new FailOnGetCompletionsProvider( new Lazy(() => codeDocumentProvider.Object), CompletionBroker, TextBufferProvider); var context = CreateContext(1, completionProvider, document); // Act & Assert await completionProvider.ProvideCompletionsAsync(context); } [Fact] public async Task ProvideCompletionAsync_DoesNotProvideCompletionsWhenDocumentProviderCanNotGetDocument() { // Arrange RazorCodeDocument codeDocument; var codeDocumentProvider = new Mock(); codeDocumentProvider.Setup(provider => provider.TryGetFromDocument(It.IsAny(), out codeDocument)) .Returns(false); var completionProvider = new FailOnGetCompletionsProvider( new Lazy(() => codeDocumentProvider.Object), CompletionBroker, TextBufferProvider); var document = CreateDocument(); var context = CreateContext(1, completionProvider, document); // Act & Assert await completionProvider.ProvideCompletionsAsync(context); } [Fact] public async Task ProvideCompletionAsync_DoesNotProvideCompletionsCanNotFindSnapshotPoint() { // Arrange var codeDocumentProvider = CreateCodeDocumentProvider("@", Enumerable.Empty()); var completionProvider = new FailOnGetCompletionsProvider( codeDocumentProvider, CompletionBroker, TextBufferProvider, canGetSnapshotPoint: false); var document = CreateDocument(); var context = CreateContext(0, completionProvider, document); // Act & Assert await completionProvider.ProvideCompletionsAsync(context); } private static Lazy CreateCodeDocumentProvider(string text, IEnumerable directives) { var codeDocumentProvider = new Mock(); var codeDocument = TestRazorCodeDocument.CreateEmpty(); var sourceDocument = TestRazorSourceDocument.Create(text); var options = RazorParserOptions.Create(builder => { foreach (var directive in directives) { builder.Directives.Add(directive); } }); var syntaxTree = RazorSyntaxTree.Parse(sourceDocument, options); codeDocument.SetSyntaxTree(syntaxTree); codeDocumentProvider.Setup(provider => provider.TryGetFromDocument(It.IsAny(), out codeDocument)) .Returns(true); return new Lazy(() => codeDocumentProvider.Object); } private static CompletionContext CreateContext(int position, RazorDirectiveCompletionProvider completionProvider, Document document) { var context = new CompletionContext( completionProvider, document, position, TextSpan.FromBounds(position, position), CompletionTrigger.Invoke, new Mock().Object, CancellationToken.None); return context; } private static Document CreateDocument() { Document document = null; TestWorkspace.Create(workspace => { var project = ProjectInfo .Create(ProjectId.CreateNewId(), VersionStamp.Default, "TestProject", "TestAssembly", LanguageNames.CSharp) .WithFilePath("/TestProject.csproj"); workspace.AddProject(project); var documentInfo = DocumentInfo.Create(DocumentId.CreateNewId(project.Id), "Test.cshtml"); document = workspace.AddDocument(documentInfo); document = document.WithFilePath("Test.cshtml"); }); return document; } private class FailOnGetCompletionsProvider : RazorDirectiveCompletionProvider { private readonly bool _canGetSnapshotPoint; public FailOnGetCompletionsProvider( Lazy codeDocumentProvider, IAsyncCompletionBroker asyncCompletionBroker, RazorTextBufferProvider textBufferProvider, bool canGetSnapshotPoint = true) : base(codeDocumentProvider, new Lazy(() => new DefaultRazorCompletionFactsService()), asyncCompletionBroker, textBufferProvider) { _canGetSnapshotPoint = canGetSnapshotPoint; } protected override bool TryGetRazorSnapshotPoint(CompletionContext context, out SnapshotPoint snapshotPoint) { if (!_canGetSnapshotPoint) { snapshotPoint = default(SnapshotPoint); return false; } var snapshot = new Mock(MockBehavior.Strict); snapshot.Setup(s => s.Length) .Returns(context.CompletionListSpan.End); snapshotPoint = new SnapshotPoint(snapshot.Object, context.CompletionListSpan.Start); return true; } } } }