public void GetElementCompletions_CapturesAllAllowedChildTagsFromParentTagHelpers_SomeTagHelpers()
        {
            // Arrange
            var documentDescriptors = new[]
            {
                TagHelperDescriptorBuilder.Create("BoldParent", "TestAssembly")
                .TagMatchingRuleDescriptor(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 = BuildElementCompletionContext(documentDescriptors, Enumerable.Empty <string>(), containingTagName: "div");
            var service           = CreateTagHelperCompletionFactsService();

            // Act
            var completions = service.GetElementCompletions(completionContext);

            // Assert
            AssertCompletionsAreEquivalent(expectedCompletions, completions);
        }
        public void GetElementCompletions_EnsuresDescriptorsHaveSatisfiedParent()
        {
            // Arrange
            var documentDescriptors = new[]
            {
                TagHelperDescriptorBuilder.Create("LiTagHelper1", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("li"))
                .Build(),
                TagHelperDescriptorBuilder.Create("LiTagHelper2", "TestAssembly")
                .TagMatchingRuleDescriptor(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   = BuildElementCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
            var service             = CreateTagHelperCompletionFactsService();

            // Act
            var completions = service.GetElementCompletions(completionContext);

            // Assert
            AssertCompletionsAreEquivalent(expectedCompletions, completions);
        }
        public void GetElementCompletions_AllowedChildrenAreIgnoredWhenAtRoot()
        {
            // Arrange
            var documentDescriptors = new[]
            {
                TagHelperDescriptorBuilder.Create("CatchAll", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("*"))
                .AllowChildTag("b")
                .AllowChildTag("bold")
                .AllowChildTag("div")
                .Build(),
            };
            var expectedCompletions = ElementCompletionResult.Create(new Dictionary <string, HashSet <TagHelperDescriptor> >());

            var existingCompletions = Enumerable.Empty <string>();
            var completionContext   = BuildElementCompletionContext(
                documentDescriptors,
                existingCompletions,
                containingTagName: null,
                containingParentTagName: null);
            var service = CreateTagHelperCompletionFactsService();

            // Act
            var completions = service.GetElementCompletions(completionContext);

            // Assert
            AssertCompletionsAreEquivalent(expectedCompletions, completions);
        }
        public void GetElementCompletions_OutputHintIsCrossReferencedWithExistingCompletions()
        {
            // Arrange
            var documentDescriptors = new[]
            {
                TagHelperDescriptorBuilder.Create("DivTagHelper", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("div"))
                .TagOutputHint("li")
                .Build(),
                TagHelperDescriptorBuilder.Create("LiTagHelper", "TestAssembly")
                .TagMatchingRuleDescriptor(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   = BuildElementCompletionContext(documentDescriptors, existingCompletions, containingTagName: "ul");
            var service             = CreateTagHelperCompletionFactsService();

            // Act
            var completions = service.GetElementCompletions(completionContext);

            // Assert
            AssertCompletionsAreEquivalent(expectedCompletions, completions);
        }
        public void GetElementCompletions_CatchAllsApplyToOnlyTagHelperCompletions()
        {
            // Arrange
            var documentDescriptors = new[]
            {
                TagHelperDescriptorBuilder.Create("SuperLiTagHelper", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("superli"))
                .Build(),
                TagHelperDescriptorBuilder.Create("CatchAll", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("*"))
                .Build(),
            };
            var expectedCompletions = ElementCompletionResult.Create(new Dictionary <string, HashSet <TagHelperDescriptor> >()
            {
                ["superli"] = new HashSet <TagHelperDescriptor>()
                {
                    documentDescriptors[0], documentDescriptors[1]
                },
                ["li"] = new HashSet <TagHelperDescriptor>(),
            });

            var existingCompletions = new[] { "li" };
            var completionContext   = BuildElementCompletionContext(
                documentDescriptors,
                existingCompletions,
                containingTagName: "ul");
            var service = CreateTagHelperCompletionFactsService();

            // Act
            var completions = service.GetElementCompletions(completionContext);

            // Assert
            AssertCompletionsAreEquivalent(expectedCompletions, completions);
        }
        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);
            }
        }
        public void GetElementCompletions_AllowsMultiTargetingTagHelpers()
        {
            // Arrange
            var documentDescriptors = new[]
            {
                TagHelperDescriptorBuilder.Create("BoldTagHelper1", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("strong"))
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("b"))
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("bold"))
                .Build(),
                TagHelperDescriptorBuilder.Create("BoldTagHelper2", "TestAssembly")
                .TagMatchingRuleDescriptor(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   = BuildElementCompletionContext(
                documentDescriptors,
                existingCompletions,
                containingTagName: "ul");
            var service = CreateTagHelperCompletionFactsService();

            // Act
            var completions = service.GetElementCompletions(completionContext);

            // Assert
            AssertCompletionsAreEquivalent(expectedCompletions, completions);
        }
        public void GetElementCompletions_TagOutputHintDoesNotFallThroughToSchemaCheck()
        {
            // Arrange
            var documentDescriptors = new[]
            {
                TagHelperDescriptorBuilder.Create("MyTableTagHelper", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("my-table"))
                .TagOutputHint("table")
                .Build(),
                TagHelperDescriptorBuilder.Create("MyTrTagHelper", "TestAssembly")
                .TagMatchingRuleDescriptor(rule => rule.RequireTagName("my-tr"))
                .TagOutputHint("tr")
                .Build(),
            };
            var expectedCompletions = ElementCompletionResult.Create(new Dictionary <string, HashSet <TagHelperDescriptor> >()
            {
                ["my-table"] = new HashSet <TagHelperDescriptor> {
                    documentDescriptors[0]
                },
                ["table"] = new HashSet <TagHelperDescriptor>(),
            });

            var existingCompletions = new[] { "table" };
            var completionContext   = BuildElementCompletionContext(
                documentDescriptors,
                existingCompletions,
                containingTagName: "body",
                containingParentTagName: null);
            var service = CreateTagHelperCompletionFactsService();

            // Act
            var completions = service.GetElementCompletions(completionContext);

            // Assert
            AssertCompletionsAreEquivalent(expectedCompletions, completions);
        }
        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 catchAllDescriptors = new HashSet <TagHelperDescriptor>();
            var prefix = completionContext.DocumentContext.Prefix ?? string.Empty;
            var possibleChildDescriptors = _tagHelperFactsService.GetTagHelpersGivenParent(completionContext.DocumentContext, completionContext.ContainingTagName);

            foreach (var possibleDescriptor in possibleChildDescriptors)
            {
                var addRuleCompletions = false;
                var outputHint         = possibleDescriptor.TagOutputHint;

                foreach (var rule in possibleDescriptor.TagMatchingRules)
                {
                    if (rule.TagName == TagHelperMatchingConventions.ElementCatchAllName)
                    {
                        catchAllDescriptors.Add(possibleDescriptor);
                    }
                    else if (elementCompletions.ContainsKey(rule.TagName))
                    {
                        addRuleCompletions = true;
                    }
                    else if (outputHint != null)
                    {
                        // If the current descriptor has an output hint we need to make sure it shows up only when its output hint would normally show up.
                        // Example: We have a MyTableTagHelper that has an output hint of "table" and a MyTrTagHelper that has an output hint of "tr".
                        // If we try typing in a situation like this: <body > | </body>
                        // We'd expect to only get "my-table" as a completion because the "body" tag doesn't allow "tr" tags.
                        addRuleCompletions = elementCompletions.ContainsKey(outputHint);
                    }
                    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)
                    {
                        UpdateCompletions(prefix + rule.TagName, possibleDescriptor);
                    }
                }
            }

            // We needed to track all catch-alls and update their completions after all other completions have been completed.
            // This way, any TagHelper added completions will also have catch-alls listed under their entries.
            foreach (var catchAllDescriptor in catchAllDescriptors)
            {
                foreach (var completionTagName in elementCompletions.Keys)
                {
                    if (elementCompletions[completionTagName].Count > 0 ||
                        !string.IsNullOrEmpty(prefix) && completionTagName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                    {
                        // The current completion either has other TagHelper's associated with it or is prefixed with a non-empty
                        // TagHelper prefix.
                        UpdateCompletions(completionTagName, catchAllDescriptor);
                    }
                }
            }

            var result = ElementCompletionResult.Create(elementCompletions);

            return(result);

            void UpdateCompletions(string tagName, TagHelperDescriptor possibleDescriptor)
            {
                if (!elementCompletions.TryGetValue(tagName, out var existingRuleDescriptors))
                {
                    existingRuleDescriptors     = new HashSet <TagHelperDescriptor>();
                    elementCompletions[tagName] = existingRuleDescriptors;
                }

                existingRuleDescriptors.Add(possibleDescriptor);
            }
        }