Add element completion API for TagHelpers.
- Added a new `TagHelperCompletionService` which can be queried for information on what completion information should be provided. - Added tests to validate the completion service's expectations. - Fixed an issue where `TagHelper`s with output hints that did not exist in a passed in completion would never be highlighted as `TagHelper`'s; even when their primary targeting element was already in the element completion list. #1181
This commit is contained in:
parent
1b8a4e704c
commit
a1cfd22a32
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
|
||||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
// 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.ComponentModel.Composition;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
|
{
|
||||||
|
[Export(typeof(TagHelperCompletionService))]
|
||||||
|
internal class DefaultTagHelperCompletionService : TagHelperCompletionService
|
||||||
|
{
|
||||||
|
private readonly TagHelperFactsService _tagHelperFactsService;
|
||||||
|
private static readonly HashSet<TagHelperDescriptor> _emptyHashSet = new HashSet<TagHelperDescriptor>();
|
||||||
|
|
||||||
|
[ImportingConstructor]
|
||||||
|
public DefaultTagHelperCompletionService(TagHelperFactsService tagHelperFactsService)
|
||||||
|
{
|
||||||
|
_tagHelperFactsService = tagHelperFactsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ElementCompletionResult GetElementCompletions(ElementCompletionContext completionContext)
|
||||||
|
{
|
||||||
|
if (completionContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(completionContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
var elementCompletions = new Dictionary<string, HashSet<TagHelperDescriptor>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
AddAllowedChildrenCompletions(completionContext, elementCompletions);
|
||||||
|
|
||||||
|
if (elementCompletions.Count > 0)
|
||||||
|
{
|
||||||
|
// If the containing element is already a TagHelper and only allows certain children.
|
||||||
|
var emptyResult = ElementCompletionResult.Create(elementCompletions);
|
||||||
|
return emptyResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
elementCompletions = completionContext.ExistingCompletions.ToDictionary(
|
||||||
|
completion => completion,
|
||||||
|
_ => new HashSet<TagHelperDescriptor>(),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var possibleChildDescriptors = _tagHelperFactsService.GetTagHelpersGivenParent(completionContext.DocumentContext, completionContext.ContainingTagName);
|
||||||
|
foreach (var possibleDescriptor in possibleChildDescriptors)
|
||||||
|
{
|
||||||
|
var addRuleCompletions = false;
|
||||||
|
var outputHint = possibleDescriptor.TagOutputHint;
|
||||||
|
|
||||||
|
// Filter out catch-all rules because TagHelpers that target attributes only would light up every child tag otherwise. Force those TagHelpers
|
||||||
|
// to have additional requirements before showing them in the element completion list.
|
||||||
|
var nonCatchAllRules = possibleDescriptor.TagMatchingRules.Where(rule => rule.TagName != TagHelperMatchingConventions.ElementCatchAllName);
|
||||||
|
foreach (var rule in nonCatchAllRules)
|
||||||
|
{
|
||||||
|
if (elementCompletions.ContainsKey(rule.TagName))
|
||||||
|
{
|
||||||
|
addRuleCompletions = true;
|
||||||
|
}
|
||||||
|
else if (outputHint != null && elementCompletions.ContainsKey(outputHint))
|
||||||
|
{
|
||||||
|
// If the possible descriptors final output tag already exists in our list of completions, we should add every representation
|
||||||
|
// of that descriptor to the possible element completions.
|
||||||
|
addRuleCompletions = true;
|
||||||
|
}
|
||||||
|
else if (!completionContext.InHTMLSchema(rule.TagName))
|
||||||
|
{
|
||||||
|
// If there is an unknown HTML schema tag that doesn't exist in the current completion we should add it. This happens for
|
||||||
|
// TagHelpers that target non-schema oriented tags.
|
||||||
|
addRuleCompletions = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addRuleCompletions)
|
||||||
|
{
|
||||||
|
if (!elementCompletions.TryGetValue(rule.TagName, out var existingRuleDescriptors))
|
||||||
|
{
|
||||||
|
existingRuleDescriptors = new HashSet<TagHelperDescriptor>();
|
||||||
|
elementCompletions[rule.TagName] = existingRuleDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingRuleDescriptors.Add(possibleDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = ElementCompletionResult.Create(elementCompletions);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAllowedChildrenCompletions(
|
||||||
|
ElementCompletionContext completionContext,
|
||||||
|
Dictionary<string, HashSet<TagHelperDescriptor>> elementCompletions)
|
||||||
|
{
|
||||||
|
if (completionContext.ContainingTagName == null)
|
||||||
|
{
|
||||||
|
// If we're at the root then there's no containing TagHelper to specify allowed children.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix = completionContext.DocumentContext.Prefix ?? string.Empty;
|
||||||
|
var binding = _tagHelperFactsService.GetTagHelperBinding(
|
||||||
|
completionContext.DocumentContext,
|
||||||
|
completionContext.ContainingTagName,
|
||||||
|
completionContext.Attributes,
|
||||||
|
completionContext.ContainingParentTagName);
|
||||||
|
|
||||||
|
if (binding == null)
|
||||||
|
{
|
||||||
|
// Containing tag is not a TagHelper; therefore, it allows any children.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var descriptor in binding.Descriptors)
|
||||||
|
{
|
||||||
|
if (descriptor.AllowedChildTags == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var childTag in descriptor.AllowedChildTags)
|
||||||
|
{
|
||||||
|
var prefixedName = string.Concat(prefix, childTag);
|
||||||
|
var descriptors = _tagHelperFactsService.GetTagHelpersGivenTag(
|
||||||
|
completionContext.DocumentContext,
|
||||||
|
prefixedName,
|
||||||
|
completionContext.ContainingTagName);
|
||||||
|
|
||||||
|
if (descriptors.Count == 0)
|
||||||
|
{
|
||||||
|
if (!elementCompletions.ContainsKey(prefixedName))
|
||||||
|
{
|
||||||
|
elementCompletions[prefixedName] = _emptyHashSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!elementCompletions.TryGetValue(prefixedName, out var existingRuleDescriptors))
|
||||||
|
{
|
||||||
|
existingRuleDescriptors = new HashSet<TagHelperDescriptor>();
|
||||||
|
elementCompletions[prefixedName] = existingRuleDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingRuleDescriptors.UnionWith(descriptors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
// 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 sealed class ElementCompletionContext
|
||||||
|
{
|
||||||
|
public ElementCompletionContext(
|
||||||
|
TagHelperDocumentContext documentContext,
|
||||||
|
IEnumerable<string> existingCompletions,
|
||||||
|
string containingTagName,
|
||||||
|
IEnumerable<KeyValuePair<string, string>> attributes,
|
||||||
|
string containingParentTagName,
|
||||||
|
Func<string, bool> inHTMLSchema)
|
||||||
|
{
|
||||||
|
if (documentContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(documentContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingCompletions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(existingCompletions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inHTMLSchema == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(inHTMLSchema));
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentContext = documentContext;
|
||||||
|
ExistingCompletions = existingCompletions;
|
||||||
|
ContainingTagName = containingTagName;
|
||||||
|
Attributes = attributes;
|
||||||
|
ContainingParentTagName = containingParentTagName;
|
||||||
|
InHTMLSchema = inHTMLSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagHelperDocumentContext DocumentContext { get; }
|
||||||
|
|
||||||
|
public IEnumerable<string> ExistingCompletions { get; }
|
||||||
|
|
||||||
|
public string ContainingTagName { get; }
|
||||||
|
|
||||||
|
public IEnumerable<KeyValuePair<string, string>> Attributes { get; }
|
||||||
|
|
||||||
|
public string ContainingParentTagName { 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 ElementCompletionResult
|
||||||
|
{
|
||||||
|
private ElementCompletionResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract IReadOnlyDictionary<string, IEnumerable<TagHelperDescriptor>> Completions { get; }
|
||||||
|
|
||||||
|
internal static ElementCompletionResult Create(Dictionary<string, HashSet<TagHelperDescriptor>> completions)
|
||||||
|
{
|
||||||
|
var readonlyCompletions = completions.ToDictionary(
|
||||||
|
key => key.Key,
|
||||||
|
value => (IEnumerable<TagHelperDescriptor>)value.Value,
|
||||||
|
completions.Comparer);
|
||||||
|
var result = new DefaultElementCompletionResult(readonlyCompletions);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DefaultElementCompletionResult : ElementCompletionResult
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyDictionary<string, IEnumerable<TagHelperDescriptor>> _completions;
|
||||||
|
|
||||||
|
public DefaultElementCompletionResult(IReadOnlyDictionary<string, IEnumerable<TagHelperDescriptor>> completions)
|
||||||
|
{
|
||||||
|
_completions = completions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyDictionary<string, IEnumerable<TagHelperDescriptor>> Completions => _completions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
|
{
|
||||||
|
public abstract class TagHelperCompletionService
|
||||||
|
{
|
||||||
|
public abstract ElementCompletionResult GetElementCompletions(ElementCompletionContext completionContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,370 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
|
{
|
||||||
|
public class DefaultTagHelperCompletionServiceTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_AllowsMultiTargetingTagHelpers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("BoldTagHelper1", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("strong"))
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("b"))
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("bold"))
|
||||||
|
.Build(),
|
||||||
|
TagHelperDescriptorBuilder.Create("BoldTagHelper2", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("strong"))
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["strong"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0], documentDescriptors[1] },
|
||||||
|
["b"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
["bold"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingCompletions = new[] { "strong", "b", "bold" };
|
||||||
|
var completionContext = BuildCompletionContext(
|
||||||
|
documentDescriptors,
|
||||||
|
existingCompletions,
|
||||||
|
containingTagName: "ul");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_CombinesDescriptorsOnExistingCompletions()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("LiTagHelper1", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("li"))
|
||||||
|
.Build(),
|
||||||
|
TagHelperDescriptorBuilder.Create("LiTagHelper2", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("li"))
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["li"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0], documentDescriptors[1] },
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingCompletions = new[] { "li" };
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_NewCompletionsForSchemaTagsNotInExistingCompletionsAreIgnored()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("SuperLiTagHelper", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("superli"))
|
||||||
|
.Build(),
|
||||||
|
TagHelperDescriptorBuilder.Create("LiTagHelper", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("li"))
|
||||||
|
.TagOutputHint("strong")
|
||||||
|
.Build(),
|
||||||
|
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["li"] = new HashSet<TagHelperDescriptor> { documentDescriptors[1] },
|
||||||
|
["superli"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingCompletions = new[] { "li" };
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_OutputHintIsCrossReferencedWithExistingCompletions()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||||
|
.TagOutputHint("li")
|
||||||
|
.Build(),
|
||||||
|
TagHelperDescriptorBuilder.Create("LiTagHelper", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("li"))
|
||||||
|
.TagOutputHint("strong")
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["div"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
["li"] = new HashSet<TagHelperDescriptor> { documentDescriptors[1] },
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingCompletions = new[] { "li" };
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_EnsuresDescriptorsHaveSatisfiedParent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("LiTagHelper1", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("li"))
|
||||||
|
.Build(),
|
||||||
|
TagHelperDescriptorBuilder.Create("LiTagHelper2", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("li").RequireParentTag("ol"))
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["li"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingCompletions = new[] { "li" };
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_AllowedChildrenAreIgnoredWhenAtRoot()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("CatchAll", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("*"))
|
||||||
|
.AllowChildTag("b")
|
||||||
|
.AllowChildTag("bold")
|
||||||
|
.AllowChildTag("div")
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["p"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
["em"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingCompletions = new[] { "p", "em" };
|
||||||
|
var completionContext = BuildCompletionContext(
|
||||||
|
documentDescriptors,
|
||||||
|
existingCompletions,
|
||||||
|
containingTagName: null,
|
||||||
|
containingParentTagName: null);
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_DoesNotReturnExistingCompletionsWhenAllowedChildren()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("BoldParent", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||||
|
.AllowChildTag("b")
|
||||||
|
.AllowChildTag("bold")
|
||||||
|
.AllowChildTag("div")
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["b"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
["bold"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
["div"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] }
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingCompletions = new[] { "p", "em" };
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, existingCompletions, containingTagName: "div");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_CapturesAllAllowedChildTagsFromParentTagHelpers_NoneTagHelpers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("BoldParent", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||||
|
.AllowChildTag("b")
|
||||||
|
.AllowChildTag("bold")
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["b"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
["bold"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_CapturesAllAllowedChildTagsFromParentTagHelpers_SomeTagHelpers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("BoldParent", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||||
|
.AllowChildTag("b")
|
||||||
|
.AllowChildTag("bold")
|
||||||
|
.AllowChildTag("div")
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["b"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
["bold"] = new HashSet<TagHelperDescriptor>(),
|
||||||
|
["div"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] }
|
||||||
|
});
|
||||||
|
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetElementCompletions_CapturesAllAllowedChildTagsFromParentTagHelpers_AllTagHelpers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentDescriptors = new[]
|
||||||
|
{
|
||||||
|
TagHelperDescriptorBuilder.Create("BoldParentCatchAll", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("*"))
|
||||||
|
.AllowChildTag("strong")
|
||||||
|
.AllowChildTag("div")
|
||||||
|
.AllowChildTag("b")
|
||||||
|
.Build(),
|
||||||
|
TagHelperDescriptorBuilder.Create("BoldParent", "TestAssembly")
|
||||||
|
.TagMatchingRule(rule => rule.RequireTagName("div"))
|
||||||
|
.AllowChildTag("b")
|
||||||
|
.AllowChildTag("bold")
|
||||||
|
.Build(),
|
||||||
|
};
|
||||||
|
var expectedCompletions = ElementCompletionResult.Create(new Dictionary<string, HashSet<TagHelperDescriptor>>()
|
||||||
|
{
|
||||||
|
["strong"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
["b"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
["bold"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0] },
|
||||||
|
["div"] = new HashSet<TagHelperDescriptor> { documentDescriptors[0], documentDescriptors[1] },
|
||||||
|
});
|
||||||
|
|
||||||
|
var completionContext = BuildCompletionContext(documentDescriptors, Enumerable.Empty<string>(), containingTagName: "div");
|
||||||
|
var service = CreateTagHelperCompletionFactsService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var completions = service.GetElementCompletions(completionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertCompletionsAreEquivalent(expectedCompletions, completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DefaultTagHelperCompletionService CreateTagHelperCompletionFactsService()
|
||||||
|
{
|
||||||
|
var tagHelperFactService = new DefaultTagHelperFactsService();
|
||||||
|
var completionFactService = new DefaultTagHelperCompletionService(tagHelperFactService);
|
||||||
|
|
||||||
|
return completionFactService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertCompletionsAreEquivalent(ElementCompletionResult expected, ElementCompletionResult 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, TagHelperDescriptorComparer.CaseSensitive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ElementCompletionContext BuildCompletionContext(
|
||||||
|
IEnumerable<TagHelperDescriptor> descriptors,
|
||||||
|
IEnumerable<string> existingCompletions,
|
||||||
|
string containingTagName,
|
||||||
|
string containingParentTagName = "body")
|
||||||
|
{
|
||||||
|
var documentContext = TagHelperDocumentContext.Create(string.Empty, descriptors);
|
||||||
|
var completionContext = new ElementCompletionContext(
|
||||||
|
documentContext,
|
||||||
|
existingCompletions,
|
||||||
|
containingTagName,
|
||||||
|
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
|
||||||
|
containingParentTagName: containingParentTagName,
|
||||||
|
inHTMLSchema: (tag) => tag == "strong" || tag == "b" || tag == "bold" || tag == "li" || tag == "div");
|
||||||
|
|
||||||
|
return completionContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue