File: Runtime\TagHelpers\TagHelperRunnerTest.cs
Web Access
Project: src\src\Razor\Razor.Runtime\test\Microsoft.AspNetCore.Razor.Runtime.Test.csproj (Microsoft.AspNetCore.Razor.Runtime.Test)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using Microsoft.AspNetCore.Razor.TagHelpers;
 
namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
 
public class TagHelperRunnerTest
{
    [Fact]
    public async Task RunAsync_CallsInitPriorToProcessAsync()
    {
        // Arrange
        var runner = new TagHelperRunner();
        var executionContext = new TagHelperExecutionContext("p", TagMode.StartTagAndEndTag);
        var incrementer = 0;
        var callbackTagHelper = new CallbackTagHelper(
            initCallback: () =>
            {
                Assert.Equal(0, incrementer);
 
                incrementer++;
            },
            processAsyncCallback: () =>
            {
                Assert.Equal(1, incrementer);
 
                incrementer++;
            });
        executionContext.Add(callbackTagHelper);
 
        // Act
        await runner.RunAsync(executionContext);
 
        // Assert
        Assert.Equal(2, incrementer);
    }
 
    public static TheoryData<int[], int[]> TagHelperOrderData
    {
        get
        {
            // tagHelperOrders, expectedTagHelperOrders
            return new()
                {
                    {
                        new[] { 1000, int.MaxValue, 0 },
                        new[] { 0, 1000, int.MaxValue }
                    },
                    {
                        new[] { int.MaxValue, int.MaxValue, int.MinValue },
                        new[] { int.MinValue, int.MaxValue, int.MaxValue }
                    },
                    {
                        new[] { 0, 0, int.MinValue },
                        new[] { int.MinValue, 0, 0 }
                    },
                    {
                        new[] { int.MinValue, -1000, 0 },
                        new[] { int.MinValue, -1000, 0 }
                    },
                    {
                        new[] { 0, 1000, int.MaxValue },
                        new[] { 0, 1000, int.MaxValue }
                    },
                    {
                        new[] { int.MaxValue, int.MinValue, int.MaxValue, -1000, int.MaxValue, 0 },
                        new[] { int.MinValue, -1000, 0, int.MaxValue, int.MaxValue, int.MaxValue }
                    },
                    {
                        new[] { 0, 0, 0, 0 },
                        new[] { 0, 0, 0, 0 }
                    },
 
                    {
                        new[] { 1000, int.MaxValue, 0, -1000, int.MinValue },
                        new[] { int.MinValue, -1000, 0, 1000, int.MaxValue }
                    },
                };
        }
    }
 
    [Theory]
    [MemberData(nameof(TagHelperOrderData))]
    public async Task RunAsync_OrdersTagHelpers(
        int[] tagHelperOrders,
        int[] expectedTagHelperOrders)
    {
        // Arrange
        var runner = new TagHelperRunner();
        var executionContext = new TagHelperExecutionContext("p", TagMode.StartTagAndEndTag);
        var processOrder = new List<int>();
 
        foreach (var order in tagHelperOrders)
        {
            var orderedTagHelper = new OrderedTagHelper(order)
            {
                ProcessOrderTracker = processOrder
            };
            executionContext.Add(orderedTagHelper);
        }
 
        // Act
        await runner.RunAsync(executionContext);
 
        // Assert
        Assert.Equal(expectedTagHelperOrders, processOrder);
    }
 
    [Theory]
    [InlineData(TagMode.SelfClosing)]
    [InlineData(TagMode.StartTagAndEndTag)]
    [InlineData(TagMode.StartTagOnly)]
    public async Task RunAsync_SetsTagHelperOutputTagMode(TagMode tagMode)
    {
        // Arrange
        var runner = new TagHelperRunner();
        var executionContext = new TagHelperExecutionContext("p", tagMode);
        var tagHelper = new TagHelperContextTouchingTagHelper();
 
        executionContext.Add(tagHelper);
        executionContext.AddTagHelperAttribute("foo", true, HtmlAttributeValueStyle.DoubleQuotes);
 
        // Act
        await runner.RunAsync(executionContext);
 
        // Assert
        Assert.Equal(tagMode, executionContext.Output.TagMode);
    }
 
    [Fact]
    public async Task RunAsync_ProcessesAllTagHelpers()
    {
        // Arrange
        var runner = new TagHelperRunner();
        var executionContext = new TagHelperExecutionContext("p", TagMode.StartTagAndEndTag);
        var executableTagHelper1 = new ExecutableTagHelper();
        var executableTagHelper2 = new ExecutableTagHelper();
 
        // Act
        executionContext.Add(executableTagHelper1);
        executionContext.Add(executableTagHelper2);
        await runner.RunAsync(executionContext);
 
        // Assert
        Assert.True(executableTagHelper1.Processed);
        Assert.True(executableTagHelper2.Processed);
    }
 
    [Fact]
    public async Task RunAsync_AllowsModificationOfTagHelperOutput()
    {
        // Arrange
        var runner = new TagHelperRunner();
        var executionContext = new TagHelperExecutionContext("p", TagMode.StartTagAndEndTag);
        var executableTagHelper = new ExecutableTagHelper();
 
        // Act
        executionContext.Add(executableTagHelper);
        executionContext.AddHtmlAttribute("class", "btn", HtmlAttributeValueStyle.DoubleQuotes);
        await runner.RunAsync(executionContext);
 
        // Assert
        var output = executionContext.Output;
        Assert.Equal("foo", output.TagName);
        Assert.Equal("somethingelse", output.Attributes["class"].Value);
        Assert.Equal("world", output.Attributes["hello"].Value);
        Assert.Equal(TagMode.SelfClosing, output.TagMode);
    }
 
    [Fact]
    public async Task RunAsync_AllowsDataRetrievalFromTagHelperContext()
    {
        // Arrange
        var runner = new TagHelperRunner();
        var executionContext = new TagHelperExecutionContext("p", TagMode.StartTagAndEndTag);
        var tagHelper = new TagHelperContextTouchingTagHelper();
 
        // Act
        executionContext.Add(tagHelper);
        executionContext.AddTagHelperAttribute("foo", true, HtmlAttributeValueStyle.DoubleQuotes);
        await runner.RunAsync(executionContext);
 
        // Assert
        Assert.Equal("True", executionContext.Output.Attributes["foo"].Value);
    }
 
    [Fact]
    public async Task RunAsync_ConfiguresTagHelperContextWithExecutionContextsItems()
    {
        // Arrange
        var runner = new TagHelperRunner();
        var executionContext = new TagHelperExecutionContext("p", TagMode.StartTagAndEndTag);
        var tagHelper = new ContextInspectingTagHelper();
        executionContext.Add(tagHelper);
 
        // Act
        await runner.RunAsync(executionContext);
 
        // Assert
        Assert.NotNull(tagHelper.ContextProcessedWith);
        Assert.Same(tagHelper.ContextProcessedWith.Items, executionContext.Items);
    }
 
    private class ExecutableTagHelper : TagHelper
    {
        public bool Processed { get; set; }
 
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            Processed = true;
 
            output.TagName = "foo";
 
            var classIndex = output.Attributes.IndexOfName("class");
            if (classIndex != -1)
            {
                output.Attributes[classIndex] = new TagHelperAttribute("class", "somethingelse");
            }
 
            output.Attributes.Add("hello", "world");
            output.TagMode = TagMode.SelfClosing;
        }
    }
 
    private class ContextInspectingTagHelper : TagHelper
    {
        public TagHelperContext ContextProcessedWith { get; set; }
 
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            ContextProcessedWith = context;
        }
    }
 
    private class TagHelperContextTouchingTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.Attributes.Add("foo", context.AllAttributes["foo"].Value.ToString());
        }
    }
 
    private class OrderedTagHelper : TagHelper
    {
        public OrderedTagHelper(int order)
        {
            Order = order;
        }
 
        public override int Order { get; }
        public IList<int> ProcessOrderTracker { get; set; }
 
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            // If using this class for testing, ensure that ProcessOrderTracker is always set prior to Process
            // execution.
            Debug.Assert(ProcessOrderTracker != null);
 
            ProcessOrderTracker.Add(Order);
        }
    }
 
    private class CallbackTagHelper : TagHelper
    {
        private readonly Action _initCallback;
        private readonly Action _processAsyncCallback;
 
        public CallbackTagHelper(Action initCallback, Action processAsyncCallback)
        {
            _initCallback = initCallback;
            _processAsyncCallback = processAsyncCallback;
        }
 
        public override void Init(TagHelperContext context)
        {
            _initCallback();
        }
 
        public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            _processAsyncCallback();
 
            return base.ProcessAsync(context, output);
        }
    }
}