Add AttributeCompletion API.
- Added a `GetAttributeCompletions` API that's consistent with current Razor's editor expectations. - Added unit tests to validate all code paths of the new `GetAttributeCompletions` method. #1120
This commit is contained in:
parent
c71f6e7c3f
commit
1795fc26c1
|
|
@ -0,0 +1,65 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
public class AttributeCompletionContext
|
||||
{
|
||||
public AttributeCompletionContext(
|
||||
TagHelperDocumentContext documentContext,
|
||||
IEnumerable<string> existingCompletions,
|
||||
string currentTagName,
|
||||
IEnumerable<KeyValuePair<string, string>> attributes,
|
||||
string currentParentTagName,
|
||||
Func<string, bool> inHTMLSchema)
|
||||
{
|
||||
if (documentContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentContext));
|
||||
}
|
||||
|
||||
if (existingCompletions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(existingCompletions));
|
||||
}
|
||||
|
||||
if (currentTagName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(currentTagName));
|
||||
}
|
||||
|
||||
if (attributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributes));
|
||||
}
|
||||
|
||||
if (inHTMLSchema == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inHTMLSchema));
|
||||
}
|
||||
|
||||
DocumentContext = documentContext;
|
||||
ExistingCompletions = existingCompletions;
|
||||
CurrentTagName = currentTagName;
|
||||
Attributes = attributes;
|
||||
CurrentParentTagName = currentParentTagName;
|
||||
InHTMLSchema = inHTMLSchema;
|
||||
}
|
||||
|
||||
public TagHelperDocumentContext DocumentContext { get; }
|
||||
|
||||
public IEnumerable<string> ExistingCompletions { get; }
|
||||
|
||||
public string CurrentTagName { get; }
|
||||
|
||||
public IEnumerable<KeyValuePair<string, string>> Attributes { get; }
|
||||
|
||||
public string CurrentParentTagName { get; }
|
||||
|
||||
public Func<string, bool> InHTMLSchema { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
public abstract class AttributeCompletionResult
|
||||
{
|
||||
private AttributeCompletionResult()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract IReadOnlyDictionary<string, IEnumerable<BoundAttributeDescriptor>> Completions { get; }
|
||||
|
||||
internal static AttributeCompletionResult Create(Dictionary<string, HashSet<BoundAttributeDescriptor>> completions)
|
||||
{
|
||||
var readonlyCompletions = completions.ToDictionary(
|
||||
key => key.Key,
|
||||
value => (IEnumerable<BoundAttributeDescriptor>)value.Value,
|
||||
completions.Comparer);
|
||||
var result = new DefaultAttributeCompletionResult(readonlyCompletions);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class DefaultAttributeCompletionResult : AttributeCompletionResult
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, IEnumerable<BoundAttributeDescriptor>> _completions;
|
||||
|
||||
public DefaultAttributeCompletionResult(IReadOnlyDictionary<string, IEnumerable<BoundAttributeDescriptor>> completions)
|
||||
{
|
||||
_completions = completions;
|
||||
}
|
||||
|
||||
public override IReadOnlyDictionary<string, IEnumerable<BoundAttributeDescriptor>> Completions => _completions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
|
|
@ -21,6 +22,109 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
_tagHelperFactsService = tagHelperFactsService;
|
||||
}
|
||||
|
||||
/*
|
||||
* This API attempts to understand a users context as they're typing in a Razor file to provide TagHelper based attribute IntelliSense.
|
||||
*
|
||||
* Scenarios for TagHelper attribute IntelliSense follows:
|
||||
* 1. TagHelperDescriptor's have matching required attribute names
|
||||
* -> Provide IntelliSense for the required attributes of those descriptors to lead users towards a TagHelperified element.
|
||||
* 2. TagHelperDescriptor entirely applies to current element. Tag name, attributes, everything is fulfilled.
|
||||
* -> Provide IntelliSense for the bound attributes for the applied descriptors.
|
||||
*
|
||||
* Within each of the above scenarios if an attribute completion has a corresponding bound attribute we associate it with the corresponding
|
||||
* BoundAttributeDescriptor. By doing this a user can see what C# type a TagHelper expects for the attribute.
|
||||
*/
|
||||
public override AttributeCompletionResult GetAttributeCompletions(AttributeCompletionContext completionContext)
|
||||
{
|
||||
if (completionContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionContext));
|
||||
}
|
||||
|
||||
var attributeCompletions = completionContext.ExistingCompletions.ToDictionary(
|
||||
completion => completion,
|
||||
_ => new HashSet<BoundAttributeDescriptor>(),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var documentContext = completionContext.DocumentContext;
|
||||
var descriptorsForTag = _tagHelperFactsService.GetTagHelpersGivenTag(documentContext, completionContext.CurrentTagName, completionContext.CurrentParentTagName);
|
||||
if (descriptorsForTag.Count == 0)
|
||||
{
|
||||
// If the current tag has no possible descriptors then we can't have any additional attributes.
|
||||
var defaultResult = AttributeCompletionResult.Create(attributeCompletions);
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
var prefix = documentContext.Prefix ?? string.Empty;
|
||||
Debug.Assert(completionContext.CurrentTagName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var applicableTagHelperBinding = _tagHelperFactsService.GetTagHelperBinding(documentContext, completionContext.CurrentTagName, completionContext.Attributes, completionContext.CurrentParentTagName);
|
||||
var applicableDescriptors = applicableTagHelperBinding?.Descriptors ?? Enumerable.Empty<TagHelperDescriptor>();
|
||||
var unprefixedTagName = completionContext.CurrentTagName.Substring(prefix.Length);
|
||||
|
||||
if (!completionContext.InHTMLSchema(unprefixedTagName) &&
|
||||
applicableDescriptors.All(descriptor => descriptor.TagOutputHint == null))
|
||||
{
|
||||
// This isn't a known HTML tag and no descriptor has an output element hint. Remove all previous completions.
|
||||
attributeCompletions.Clear();
|
||||
}
|
||||
|
||||
for (var i = 0; i < descriptorsForTag.Count; i++)
|
||||
{
|
||||
var descriptor = descriptorsForTag[i];
|
||||
|
||||
if (applicableDescriptors.Contains(descriptor))
|
||||
{
|
||||
foreach (var attributeDescriptor in descriptor.BoundAttributes)
|
||||
{
|
||||
UpdateCompletions(attributeDescriptor.Name, attributeDescriptor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var htmlNameToBoundAttribute = descriptor.BoundAttributes.ToDictionary(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var rule in descriptor.TagMatchingRules)
|
||||
{
|
||||
foreach (var requiredAttribute in rule.Attributes)
|
||||
{
|
||||
if (htmlNameToBoundAttribute.TryGetValue(requiredAttribute.Name, out var attributeDescriptor))
|
||||
{
|
||||
UpdateCompletions(requiredAttribute.Name, attributeDescriptor);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateCompletions(requiredAttribute.Name, possibleDescriptor: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var completionResult = AttributeCompletionResult.Create(attributeCompletions);
|
||||
return completionResult;
|
||||
|
||||
void UpdateCompletions(string attributeName, BoundAttributeDescriptor possibleDescriptor)
|
||||
{
|
||||
if (completionContext.Attributes.Any(attribute => string.Equals(attribute.Key, attributeName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Attribute is already present on this element it shouldn't exist in the completion list.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!attributeCompletions.TryGetValue(attributeName, out var rules))
|
||||
{
|
||||
rules = new HashSet<BoundAttributeDescriptor>();
|
||||
attributeCompletions[attributeName] = rules;
|
||||
}
|
||||
|
||||
if (possibleDescriptor != null)
|
||||
{
|
||||
rules.Add(possibleDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override ElementCompletionResult GetElementCompletions(ElementCompletionContext completionContext)
|
||||
{
|
||||
if (completionContext == null)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
{
|
||||
public abstract class TagHelperCompletionService
|
||||
{
|
||||
public abstract AttributeCompletionResult GetAttributeCompletions(AttributeCompletionContext completionContext);
|
||||
|
||||
public abstract ElementCompletionResult GetElementCompletions(ElementCompletionContext completionContext);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,399 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
{
|
||||
public class DefaultTagHelperCompletionServiceTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_DoesNotReturnCompletionsForAlreadySuppliedAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("div")
|
||||
.RequireAttribute(attribute => attribute.Name("repeat")))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("visible")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Visible"))
|
||||
.Build(),
|
||||
TagHelperDescriptorBuilder.Create("StyleTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule.RequireTagName("*"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("class")
|
||||
.TypeName(typeof(string).FullName)
|
||||
.PropertyName("Class"))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["onclick"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
["visible"] = new HashSet<BoundAttributeDescriptor>()
|
||||
{
|
||||
documentDescriptors[0].BoundAttributes.Last()
|
||||
}
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "onclick" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
attributes: new Dictionary<string, string>()
|
||||
{
|
||||
["class"] = "something",
|
||||
["repeat"] = "4"
|
||||
},
|
||||
currentTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_PossibleDescriptorsReturnUnboundRequiredAttributesWithExistingCompletions()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("div")
|
||||
.RequireAttribute(attribute => attribute.Name("repeat")))
|
||||
.Build(),
|
||||
TagHelperDescriptorBuilder.Create("StyleTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("*")
|
||||
.RequireAttribute(attribute => attribute.Name("class")))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
["onclick"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
["repeat"] = new HashSet<BoundAttributeDescriptor>()
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "onclick", "class" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_PossibleDescriptorsReturnBoundRequiredAttributesWithExistingCompletions()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("div")
|
||||
.RequireAttribute(attribute => attribute.Name("repeat")))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("repeat")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Repeat"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("visible")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Visible"))
|
||||
.Build(),
|
||||
TagHelperDescriptorBuilder.Create("StyleTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("*")
|
||||
.RequireAttribute(attribute => attribute.Name("class")))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("class")
|
||||
.TypeName(typeof(string).FullName)
|
||||
.PropertyName("Class"))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(documentDescriptors[1].BoundAttributes),
|
||||
["onclick"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
["repeat"] = new HashSet<BoundAttributeDescriptor>()
|
||||
{
|
||||
documentDescriptors[0].BoundAttributes.First()
|
||||
}
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "onclick" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_AppliedDescriptorsReturnAllBoundAttributesWithExistingCompletionsForSchemaTags()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("repeat")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Repeat"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("visible")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Visible"))
|
||||
.Build(),
|
||||
TagHelperDescriptorBuilder.Create("StyleTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("*")
|
||||
.RequireAttribute(attribute => attribute.Name("class")))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("class")
|
||||
.TypeName(typeof(string).FullName)
|
||||
.PropertyName("Class"))
|
||||
.Build(),
|
||||
TagHelperDescriptorBuilder.Create("StyleTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule.RequireTagName("*"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("visible")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Visible"))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["onclick"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(documentDescriptors[1].BoundAttributes),
|
||||
["repeat"] = new HashSet<BoundAttributeDescriptor>()
|
||||
{
|
||||
documentDescriptors[0].BoundAttributes.First()
|
||||
},
|
||||
["visible"] = new HashSet<BoundAttributeDescriptor>()
|
||||
{
|
||||
documentDescriptors[0].BoundAttributes.Last(),
|
||||
documentDescriptors[2].BoundAttributes.First(),
|
||||
}
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "class", "onclick" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_AppliedTagOutputHintDescriptorsReturnBoundAttributesWithExistingCompletionsForNonSchemaTags()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("CustomTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule.RequireTagName("custom"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("repeat")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Repeat"))
|
||||
.TagOutputHint("div")
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
["repeat"] = new HashSet<BoundAttributeDescriptor>(documentDescriptors[0].BoundAttributes)
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "class" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "custom");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_AppliedDescriptorsReturnBoundAttributesCompletionsForNonSchemaTags()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("CustomTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule.RequireTagName("custom"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("repeat")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Repeat"))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["repeat"] = new HashSet<BoundAttributeDescriptor>(documentDescriptors[0].BoundAttributes)
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "class" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "custom");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_AppliedDescriptorsReturnBoundAttributesWithExistingCompletionsForSchemaTags()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||
.BindAttribute(attribute => attribute
|
||||
.Name("repeat")
|
||||
.TypeName(typeof(bool).FullName)
|
||||
.PropertyName("Repeat"))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
["repeat"] = new HashSet<BoundAttributeDescriptor>(documentDescriptors[0].BoundAttributes)
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "class" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_NoDescriptorsReturnsExistingCompletions()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "class" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
Enumerable.Empty<TagHelperDescriptor>(),
|
||||
existingCompletions,
|
||||
currentTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_NoDescriptorsForUnprefixedTagReturnsExistingCompletions()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("div")
|
||||
.RequireAttribute(attribute => attribute.Name("special")))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "class" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "div",
|
||||
tagHelperPrefix: "th:");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAttributeCompletions_NoDescriptorsForTagReturnsExistingCompletions()
|
||||
{
|
||||
// Arrange
|
||||
var documentDescriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("MyTableTagHelper", "TestAssembly")
|
||||
.TagMatchingRule(rule => rule
|
||||
.RequireTagName("table")
|
||||
.RequireAttribute(attribute => attribute.Name("special")))
|
||||
.Build(),
|
||||
};
|
||||
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
|
||||
{
|
||||
["class"] = new HashSet<BoundAttributeDescriptor>(),
|
||||
});
|
||||
|
||||
var existingCompletions = new[] { "class" };
|
||||
var completionContext = BuildAttributeCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
currentTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
var completions = service.GetAttributeCompletions(completionContext);
|
||||
|
||||
// Assert
|
||||
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetElementCompletions_TagOutputHintDoesNotFallThroughToSchemaCheck()
|
||||
{
|
||||
|
|
@ -32,7 +425,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "table" };
|
||||
var completionContext = BuildCompletionContext(
|
||||
var completionContext = BuildElementCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
containingTagName: "body",
|
||||
|
|
@ -66,7 +459,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "li" };
|
||||
var completionContext = BuildCompletionContext(
|
||||
var completionContext = BuildElementCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
containingTagName: "ul",
|
||||
|
|
@ -101,7 +494,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "li" };
|
||||
var completionContext = BuildCompletionContext(
|
||||
var completionContext = BuildElementCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
containingTagName: "ul",
|
||||
|
|
@ -135,7 +528,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "li" };
|
||||
var completionContext = BuildCompletionContext(
|
||||
var completionContext = BuildElementCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
containingTagName: "ul");
|
||||
|
|
@ -171,7 +564,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "strong", "b", "bold" };
|
||||
var completionContext = BuildCompletionContext(
|
||||
var completionContext = BuildElementCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
containingTagName: "ul");
|
||||
|
|
@ -203,7 +596,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "li" };
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -237,7 +630,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "li" };
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -269,7 +662,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "li" };
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -298,7 +691,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "li" };
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -324,7 +717,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>());
|
||||
|
||||
var existingCompletions = Enumerable.Empty<string>();
|
||||
var completionContext = BuildCompletionContext(
|
||||
var completionContext = BuildElementCompletionContext(
|
||||
documentDescriptors,
|
||||
existingCompletions,
|
||||
containingTagName: null,
|
||||
|
|
@ -359,7 +752,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
});
|
||||
|
||||
var existingCompletions = new[] { "p", "em" };
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "div");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, existingCompletions, containingTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -387,7 +780,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
["bold"] = new HashSet<TagHelperDescriptor>(),
|
||||
});
|
||||
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -417,7 +810,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
["div"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] }
|
||||
});
|
||||
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -453,7 +846,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
["div"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0], documentDescriptors[1] },
|
||||
});
|
||||
|
||||
var completionContext = BuildCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||
var completionContext = BuildElementCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||
var service = CreateTagHelperCompletionFactsService();
|
||||
|
||||
// Act
|
||||
|
|
@ -483,7 +876,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
}
|
||||
}
|
||||
|
||||
private static ElementCompletionContext BuildCompletionContext(
|
||||
private static void AssertCompletionsAreEquivalent(AttributeCompletionResult expected, AttributeCompletionResult actual)
|
||||
{
|
||||
Assert.Equal(expected.Completions.Count, actual.Completions.Count);
|
||||
|
||||
foreach (var expectedCompletion in expected.Completions)
|
||||
{
|
||||
var actualValue = actual.Completions[expectedCompletion.Key];
|
||||
Assert.NotNull(actualValue);
|
||||
Assert.Equal(expectedCompletion.Value, actualValue, BoundAttributeDescriptorComparer.CaseSensitive);
|
||||
}
|
||||
}
|
||||
|
||||
private static ElementCompletionContext BuildElementCompletionContext(
|
||||
IEnumerable<TagHelperDescriptor> descriptors,
|
||||
IEnumerable<string> existingCompletions,
|
||||
string containingTagName,
|
||||
|
|
@ -501,5 +906,25 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
|
||||
return completionContext;
|
||||
}
|
||||
|
||||
private static AttributeCompletionContext BuildAttributeCompletionContext(
|
||||
IEnumerable<TagHelperDescriptor> descriptors,
|
||||
IEnumerable<string> existingCompletions,
|
||||
string currentTagName,
|
||||
IEnumerable<KeyValuePair<string, string>> attributes = null,
|
||||
string tagHelperPrefix = "")
|
||||
{
|
||||
attributes = attributes ?? Enumerable.Empty<KeyValuePair<string, string>>();
|
||||
var documentContext = TagHelperDocumentContext.Create(tagHelperPrefix, descriptors);
|
||||
var completionContext = new AttributeCompletionContext(
|
||||
documentContext,
|
||||
existingCompletions,
|
||||
currentTagName,
|
||||
attributes,
|
||||
currentParentTagName: "body",
|
||||
inHTMLSchema: (tag) => tag == "strong" || tag == "b" || tag == "bold" || tag == "li" || tag == "div");
|
||||
|
||||
return completionContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue