Unit tests for VCTH and model directive
This commit is contained in:
parent
3ade209bf0
commit
4c62eb89af
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
return "dynamic";
|
return "dynamic";
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Pass : IRazorIRPass
|
internal class Pass : IRazorIRPass
|
||||||
{
|
{
|
||||||
public RazorEngine Engine { get; set; }
|
public RazorEngine Engine { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
writer.WriteAutoPropertyDeclaration(
|
writer.WriteAutoPropertyDeclaration(
|
||||||
"public",
|
"public",
|
||||||
$"global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext",
|
$"global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext",
|
||||||
"_context");
|
"ViewContext");
|
||||||
|
|
||||||
var indexerAttributes = descriptor.Attributes.Where(a => a.IsIndexer);
|
var indexerAttributes = descriptor.Attributes.Where(a => a.IsIndexer);
|
||||||
|
|
||||||
|
|
@ -187,19 +187,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
writer.WriteInstanceMethodInvocation(
|
writer.WriteInstanceMethodInvocation(
|
||||||
$"(_helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?",
|
$"(_helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?",
|
||||||
"Contextualize",
|
"Contextualize",
|
||||||
new[] { "_context" });
|
new[] { "ViewContext" });
|
||||||
|
|
||||||
var methodParameters = GetMethodParameters(descriptor);
|
var methodParameters = GetMethodParameters(descriptor);
|
||||||
var viewContentVariable = "viewContent";
|
var contentVariable = "content";
|
||||||
writer.Write("var ")
|
writer.Write("var ")
|
||||||
.WriteStartAssignment(viewContentVariable)
|
.WriteStartAssignment(contentVariable)
|
||||||
.WriteInstanceMethodInvocation($"await _helper", "InvokeAsync", methodParameters);
|
.WriteInstanceMethodInvocation($"await _helper", "InvokeAsync", methodParameters);
|
||||||
writer.WriteStartAssignment($"{outputVariable}.TagName")
|
writer.WriteStartAssignment($"{outputVariable}.TagName")
|
||||||
.WriteLine("null;");
|
.WriteLine("null;");
|
||||||
writer.WriteInstanceMethodInvocation(
|
writer.WriteInstanceMethodInvocation(
|
||||||
$"{outputVariable}.Content",
|
$"{outputVariable}.Content",
|
||||||
"SetHtmlContent",
|
"SetHtmlContent",
|
||||||
new[] { viewContentVariable });
|
new[] { contentVariable });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +207,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
{
|
{
|
||||||
var propertyNames = descriptor.Attributes.Where(a => !a.IsIndexer).Select(attribute => attribute.PropertyName);
|
var propertyNames = descriptor.Attributes.Where(a => !a.IsIndexer).Select(attribute => attribute.PropertyName);
|
||||||
var joinedPropertyNames = string.Join(", ", propertyNames);
|
var joinedPropertyNames = string.Join(", ", propertyNames);
|
||||||
var parametersString = $" new {{ { joinedPropertyNames } }}";
|
var parametersString = $"new {{ { joinedPropertyNames } }}";
|
||||||
|
|
||||||
var viewComponentName = descriptor.PropertyBag[
|
var viewComponentName = descriptor.PropertyBag[
|
||||||
ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
|
||||||
|
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
// 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.Chunks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Directives
|
|
||||||
{
|
|
||||||
public class SetBaseTypeChunkMergerTest
|
|
||||||
{
|
|
||||||
#if OLD_RAZOR
|
|
||||||
[Theory]
|
|
||||||
[InlineData("MyApp.BaseType<TModel>", "MyApp.BaseType<Person>")]
|
|
||||||
[InlineData("TestBaseType", "TestBaseType")]
|
|
||||||
public void Visit_UpdatesTModelTokenToMatchModelType(string typeName, string expectedValue)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var chunk = new SetBaseTypeChunk
|
|
||||||
{
|
|
||||||
TypeName = typeName,
|
|
||||||
};
|
|
||||||
var merger = new SetBaseTypeChunkMerger("Person");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
merger.VisitChunk(chunk);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedValue, chunk.TypeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Merge_SetsBaseTypeIfItHasNotBeenSetInChunkTree()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected = "MyApp.Razor.MyBaseType";
|
|
||||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
|
||||||
var chunkTree = new ChunkTree();
|
|
||||||
var inheritedChunks = new[]
|
|
||||||
{
|
|
||||||
new SetBaseTypeChunk { TypeName = expected }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var chunk = Assert.Single(chunkTree.Children);
|
|
||||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
|
|
||||||
Assert.Equal(expected, setBaseTypeChunk.TypeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Merge_IgnoresSetBaseTypeChunksIfChunkTreeContainsOne()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
|
||||||
var chunkTree = new ChunkTree();
|
|
||||||
var inheritedChunks = new[]
|
|
||||||
{
|
|
||||||
new SetBaseTypeChunk { TypeName = "MyBaseType2" }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
merger.VisitChunk(new SetBaseTypeChunk { TypeName = "MyBaseType1" });
|
|
||||||
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(chunkTree.Children);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Merge_PicksLastBaseTypeChunkFromChunkTree()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var merger = new SetBaseTypeChunkMerger("dynamic");
|
|
||||||
var chunkTree = new ChunkTree();
|
|
||||||
var inheritedChunks = new Chunk[]
|
|
||||||
{
|
|
||||||
new SetBaseTypeChunk { TypeName = "MyBase2" },
|
|
||||||
new LiteralChunk(),
|
|
||||||
new SetBaseTypeChunk { TypeName = "MyBase1" },
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var chunk = Assert.Single(chunkTree.Children);
|
|
||||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
|
|
||||||
Assert.Equal("MyBase1", setBaseTypeChunk.TypeName);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// 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.Reflection;
|
|
||||||
using Microsoft.AspNetCore.Razor.Chunks;
|
|
||||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Test.Internal
|
|
||||||
{
|
|
||||||
public class ViewComponentTagHelperChunkVisitorTest
|
|
||||||
{
|
|
||||||
#if OLD_RAZOR
|
|
||||||
public static TheoryData CodeGenerationData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var oneInstanceChunks = ChunkVisitorTestFactory.GetTestChunks(visitedTagHelperChunks: true);
|
|
||||||
var twoInstanceChunks = ChunkVisitorTestFactory.GetTestChunks(visitedTagHelperChunks: true);
|
|
||||||
twoInstanceChunks.Add(twoInstanceChunks[twoInstanceChunks.Count - 1]);
|
|
||||||
|
|
||||||
return new TheoryData<IList<Chunk>>
|
|
||||||
{
|
|
||||||
oneInstanceChunks,
|
|
||||||
twoInstanceChunks
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(CodeGenerationData))]
|
|
||||||
public void Accept_CorrectlyGeneratesCode(IList<Chunk> chunks)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var writer = new CSharpCodeWriter();
|
|
||||||
var context = ChunkVisitorTestFactory.CreateCodeGeneratorContext();
|
|
||||||
var chunkVisitor = new ViewComponentTagHelperChunkVisitor(writer, context);
|
|
||||||
|
|
||||||
var assembly = typeof(ViewComponentTagHelperChunkVisitorTest).GetTypeInfo().Assembly;
|
|
||||||
var path = "TestFiles/Output/Runtime/GeneratedViewComponentTagHelperClasses.cs";
|
|
||||||
var expectedOutput = ResourceFile.ReadResource(assembly, path, sourceFile: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
chunkVisitor.Accept(chunks);
|
|
||||||
var resultOutput = writer.GenerateCode();
|
|
||||||
|
|
||||||
#if GENERATE_BASELINES
|
|
||||||
if (!string.Equals(expectedOutput, resultOutput, System.StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
ResourceFile.UpdateFile(assembly, path, expectedOutput, resultOutput);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedOutput, resultOutput, ignoreLineEndingDifferences: true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
|
{
|
||||||
|
public class ModelDirectiveTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ModelDirective_GetModelType_GetsTypeFromLastWellFormedDirective()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@model Type1
|
||||||
|
@model Type2
|
||||||
|
@model
|
||||||
|
");
|
||||||
|
|
||||||
|
var engine = CreateEngine();
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ModelDirective.GetModelType(irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Type2", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelDirective_GetModelType_DefaultsToDynamic()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@" ");
|
||||||
|
|
||||||
|
var engine = CreateEngine();
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ModelDirective.GetModelType(irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("dynamic", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelDirectivePass_Execute_ReplacesTModelInBaseType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@inherits BaseType<TModel>
|
||||||
|
@model Type1
|
||||||
|
");
|
||||||
|
|
||||||
|
var engine = CreateEngine();
|
||||||
|
var pass = new ModelDirective.Pass()
|
||||||
|
{
|
||||||
|
Engine = engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
pass.Execute(codeDocument, irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var @class = FindClassNode(irDocument);
|
||||||
|
Assert.NotNull(@class);
|
||||||
|
Assert.Equal("BaseType<Type1>", @class.BaseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelDirectivePass_Execute_ReplacesTModelInBaseType_DifferentOrdering()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@model Type1
|
||||||
|
@inherits BaseType<TModel>
|
||||||
|
@model Type2
|
||||||
|
");
|
||||||
|
|
||||||
|
var engine = CreateEngine();
|
||||||
|
var pass = new ModelDirective.Pass()
|
||||||
|
{
|
||||||
|
Engine = engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
pass.Execute(codeDocument, irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var @class = FindClassNode(irDocument);
|
||||||
|
Assert.NotNull(@class);
|
||||||
|
Assert.Equal("BaseType<Type2>", @class.BaseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelDirectivePass_Execute_NoOpWithoutTModel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@inherits BaseType
|
||||||
|
@model Type1
|
||||||
|
");
|
||||||
|
|
||||||
|
var engine = CreateEngine();
|
||||||
|
var pass = new ModelDirective.Pass()
|
||||||
|
{
|
||||||
|
Engine = engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
pass.Execute(codeDocument, irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var @class = FindClassNode(irDocument);
|
||||||
|
Assert.NotNull(@class);
|
||||||
|
Assert.Equal("BaseType", @class.BaseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ModelDirectivePass_Execute_ReplacesTModelInBaseType_DefaultDynamic()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@inherits BaseType<TModel>
|
||||||
|
");
|
||||||
|
|
||||||
|
var engine = CreateEngine();
|
||||||
|
var pass = new ModelDirective.Pass()
|
||||||
|
{
|
||||||
|
Engine = engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
pass.Execute(codeDocument, irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var @class = FindClassNode(irDocument);
|
||||||
|
Assert.NotNull(@class);
|
||||||
|
Assert.Equal("BaseType<dynamic>", @class.BaseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RazorCodeDocument CreateDocument(string content)
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
stream.Seek(0L, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var source = RazorSourceDocument.ReadFrom(stream, "test.cshtml");
|
||||||
|
return RazorCodeDocument.Create(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassDeclarationIRNode FindClassNode(RazorIRNode node)
|
||||||
|
{
|
||||||
|
var visitor = new ClassNodeVisitor();
|
||||||
|
visitor.Visit(node);
|
||||||
|
return visitor.Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RazorEngine CreateEngine()
|
||||||
|
{
|
||||||
|
return RazorEngine.Create(b =>
|
||||||
|
{
|
||||||
|
// Notice we're not registering the ModelDirective.Pass here so we can run it on demand.
|
||||||
|
b.AddDirective(ModelDirective.Directive);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < engine.Phases.Count; i++)
|
||||||
|
{
|
||||||
|
var phase = engine.Phases[i];
|
||||||
|
phase.Execute(codeDocument);
|
||||||
|
|
||||||
|
if (phase is IRazorIRPhase)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeDocument.GetIRDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCSharpContent(RazorIRNode node)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = node.Children[i] as CSharpTokenIRNode;
|
||||||
|
builder.Append(child.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClassNodeVisitor : RazorIRNodeWalker
|
||||||
|
{
|
||||||
|
public ClassDeclarationIRNode Node { get; set; }
|
||||||
|
|
||||||
|
public override void VisitClass(ClassDeclarationIRNode node)
|
||||||
|
{
|
||||||
|
Node = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
// 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 Microsoft.AspNetCore.Razor.Text;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
|
||||||
{
|
|
||||||
public class StringTextBuffer : ITextBuffer, IDisposable
|
|
||||||
{
|
|
||||||
private string _buffer;
|
|
||||||
public bool Disposed { get; set; }
|
|
||||||
|
|
||||||
public StringTextBuffer(string buffer)
|
|
||||||
{
|
|
||||||
_buffer = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Length
|
|
||||||
{
|
|
||||||
get { return _buffer.Length; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Position { get; set; }
|
|
||||||
|
|
||||||
public int Read()
|
|
||||||
{
|
|
||||||
if (Position >= _buffer.Length)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return _buffer[Position++];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Peek()
|
|
||||||
{
|
|
||||||
if (Position >= _buffer.Length)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return _buffer[Position];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("foo")]
|
|
||||||
public class __Generated__FooViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper
|
|
||||||
{
|
|
||||||
private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper _viewComponentHelper = null;
|
|
||||||
public __Generated__FooViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper viewComponentHelper)
|
|
||||||
{
|
|
||||||
_viewComponentHelper = viewComponentHelper;
|
|
||||||
}
|
|
||||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
|
|
||||||
public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; }
|
|
||||||
public System.String Attribute { get; set; }
|
|
||||||
public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output)
|
|
||||||
{
|
|
||||||
(_viewComponentHelper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?.Contextualize(ViewContext);
|
|
||||||
var viewContent = await _viewComponentHelper.InvokeAsync("Foo", new { Attribute });
|
|
||||||
output.TagName = null;
|
|
||||||
output.Content.SetHtmlContent(viewContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("bar")]
|
|
||||||
public class __Generated__BarViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper
|
|
||||||
{
|
|
||||||
private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper _viewComponentHelper = null;
|
|
||||||
public __Generated__BarViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper viewComponentHelper)
|
|
||||||
{
|
|
||||||
_viewComponentHelper = viewComponentHelper;
|
|
||||||
}
|
|
||||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
|
|
||||||
public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; }
|
|
||||||
public System.String Attribute { get; set; }
|
|
||||||
public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output)
|
|
||||||
{
|
|
||||||
(_viewComponentHelper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?.Contextualize(ViewContext);
|
|
||||||
var viewContent = await _viewComponentHelper.InvokeAsync("Bar", new { Attribute });
|
|
||||||
output.TagName = null;
|
|
||||||
output.Content.SetHtmlContent(viewContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("bee")]
|
|
||||||
public class __Generated__BeeViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper
|
|
||||||
{
|
|
||||||
private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper _viewComponentHelper = null;
|
|
||||||
public __Generated__BeeViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper viewComponentHelper)
|
|
||||||
{
|
|
||||||
_viewComponentHelper = viewComponentHelper;
|
|
||||||
}
|
|
||||||
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
|
|
||||||
public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; }
|
|
||||||
public System.Collections.Generic.Dictionary<global::System.String, global::System.Collections.Generic.List<global::System.Int32>> Attribute { get; set; }
|
|
||||||
= new System.Collections.Generic.Dictionary<global::System.String, global::System.Collections.Generic.List<global::System.Int32>>();
|
|
||||||
public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output)
|
|
||||||
{
|
|
||||||
(_viewComponentHelper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?.Contextualize(ViewContext);
|
|
||||||
var viewContent = await _viewComponentHelper.InvokeAsync("Bee", new { Attribute });
|
|
||||||
output.TagName = null;
|
|
||||||
output.Content.SetHtmlContent(viewContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,336 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
|
||||||
|
using Xunit;
|
||||||
|
using ErrorSink = Microsoft.AspNetCore.Razor.Evolution.Legacy.ErrorSink;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
|
{
|
||||||
|
public class ViewComponentTagHelperPassTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ViewComponentTagHelperPass_Execute_IgnoresRegularTagHelper()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@addTagHelper TestTagHelper, TestAssembly
|
||||||
|
<p foo=""17"">");
|
||||||
|
|
||||||
|
var tagHelpers = new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor()
|
||||||
|
{
|
||||||
|
AssemblyName = "TestAssembly",
|
||||||
|
TypeName = "TestTagHelper",
|
||||||
|
TagName = "p",
|
||||||
|
Attributes = new TagHelperAttributeDescriptor[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor()
|
||||||
|
{
|
||||||
|
TypeName = "System.Int32",
|
||||||
|
Name = "Foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var engine = CreateEngine(tagHelpers);
|
||||||
|
var pass = new ViewComponentTagHelperPass()
|
||||||
|
{
|
||||||
|
Engine = engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
pass.Execute(codeDocument, irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var @class = FindClassNode(irDocument);
|
||||||
|
Assert.Equal(2, @class.Children.Count); // No class node created for a VCTH
|
||||||
|
for (var i = 0; i < @class.Children.Count; i++)
|
||||||
|
{
|
||||||
|
Assert.IsNotType<CSharpStatementIRNode>(@class.Children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ViewComponentTagHelperPass_Execute_CreatesViewComponentTagHelper()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@addTagHelper TestTagHelper, TestAssembly
|
||||||
|
<tagcloud foo=""17"">");
|
||||||
|
|
||||||
|
var tagHelpers = new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor()
|
||||||
|
{
|
||||||
|
AssemblyName = "TestAssembly",
|
||||||
|
TypeName = "TestTagHelper",
|
||||||
|
TagName = "tagcloud",
|
||||||
|
Attributes = new TagHelperAttributeDescriptor[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor()
|
||||||
|
{
|
||||||
|
TypeName = "System.Int32",
|
||||||
|
Name = "Foo",
|
||||||
|
PropertyName = "Foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tagHelpers[0].PropertyBag.Add(ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, "TagCloud");
|
||||||
|
|
||||||
|
var engine = CreateEngine(tagHelpers);
|
||||||
|
var pass = new ViewComponentTagHelperPass()
|
||||||
|
{
|
||||||
|
Engine = engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
var expectedVCTHName = "AspNetCore.Generated_test.__Generated__TagCloudViewComponentTagHelper";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
pass.Execute(codeDocument, irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var tagHelper = FindTagHelperNode(irDocument);
|
||||||
|
Assert.Equal(expectedVCTHName, Assert.IsType<CreateTagHelperIRNode>(tagHelper.Children[1]).TagHelperTypeName);
|
||||||
|
Assert.Equal(expectedVCTHName, Assert.IsType<SetTagHelperPropertyIRNode>(tagHelper.Children[2]).TagHelperTypeName);
|
||||||
|
|
||||||
|
|
||||||
|
var @class = FindClassNode(irDocument);
|
||||||
|
Assert.Equal(3, @class.Children.Count);
|
||||||
|
|
||||||
|
var vcthClass = Assert.IsType<CSharpStatementIRNode>(@class.Children[2]);
|
||||||
|
Assert.Equal(@"[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute(""tagcloud"")]
|
||||||
|
public class __Generated__TagCloudViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper
|
||||||
|
{
|
||||||
|
private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper _helper = null;
|
||||||
|
public __Generated__TagCloudViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper helper)
|
||||||
|
{
|
||||||
|
_helper = helper;
|
||||||
|
}
|
||||||
|
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; }
|
||||||
|
public System.Int32 Foo { get; set; }
|
||||||
|
public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output)
|
||||||
|
{
|
||||||
|
(_helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?.Contextualize(ViewContext);
|
||||||
|
var content = await _helper.InvokeAsync(""TagCloud"", new { Foo });
|
||||||
|
output.TagName = null;
|
||||||
|
output.Content.SetHtmlContent(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
", vcthClass.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ViewComponentTagHelperPass_Execute_CreatesViewComponentTagHelper_WithIndexer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var codeDocument = CreateDocument(@"
|
||||||
|
@addTagHelper TestTagHelper, TestAssembly
|
||||||
|
<tagcloud tag-foo=""17"">");
|
||||||
|
|
||||||
|
var tagHelpers = new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor()
|
||||||
|
{
|
||||||
|
AssemblyName = "TestAssembly",
|
||||||
|
TypeName = "TestTagHelper",
|
||||||
|
TagName = "tagcloud",
|
||||||
|
Attributes = new TagHelperAttributeDescriptor[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor()
|
||||||
|
{
|
||||||
|
TypeName = "System.Collections.Generic.Dictionary<System.String, System.Int32>",
|
||||||
|
Name = "foo",
|
||||||
|
PropertyName = "Tags",
|
||||||
|
IsIndexer = false,
|
||||||
|
},
|
||||||
|
new TagHelperAttributeDescriptor()
|
||||||
|
{
|
||||||
|
TypeName = "System.Collections.Generic.Dictionary<System.String, System.Int32>",
|
||||||
|
Name = "foo-",
|
||||||
|
PropertyName = "Tags",
|
||||||
|
IsIndexer = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tagHelpers[0].PropertyBag.Add(ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey, "TagCloud");
|
||||||
|
|
||||||
|
var engine = CreateEngine(tagHelpers);
|
||||||
|
var pass = new ViewComponentTagHelperPass()
|
||||||
|
{
|
||||||
|
Engine = engine,
|
||||||
|
};
|
||||||
|
|
||||||
|
var irDocument = CreateIRDocument(engine, codeDocument);
|
||||||
|
|
||||||
|
var expectedVCTHName = "AspNetCore.Generated_test.__Generated__TagCloudViewComponentTagHelper";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
pass.Execute(codeDocument, irDocument);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var tagHelper = FindTagHelperNode(irDocument);
|
||||||
|
Assert.Equal(expectedVCTHName, Assert.IsType<CreateTagHelperIRNode>(tagHelper.Children[1]).TagHelperTypeName);
|
||||||
|
Assert.IsType<AddPreallocatedTagHelperHtmlAttributeIRNode>(tagHelper.Children[2]);
|
||||||
|
|
||||||
|
var @class = FindClassNode(irDocument);
|
||||||
|
Assert.Equal(4, @class.Children.Count);
|
||||||
|
|
||||||
|
var vcthClass = Assert.IsType<CSharpStatementIRNode>(@class.Children[3]);
|
||||||
|
Assert.Equal(@"[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute(""tagcloud"")]
|
||||||
|
public class __Generated__TagCloudViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper
|
||||||
|
{
|
||||||
|
private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper _helper = null;
|
||||||
|
public __Generated__TagCloudViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper helper)
|
||||||
|
{
|
||||||
|
_helper = helper;
|
||||||
|
}
|
||||||
|
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
|
||||||
|
public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; }
|
||||||
|
public System.Collections.Generic.Dictionary<System.String, System.Int32> Tags { get; set; }
|
||||||
|
= new System.Collections.Generic.Dictionary<System.String, System.Int32>();
|
||||||
|
public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output)
|
||||||
|
{
|
||||||
|
(_helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?.Contextualize(ViewContext);
|
||||||
|
var content = await _helper.InvokeAsync(""TagCloud"", new { Tags });
|
||||||
|
output.TagName = null;
|
||||||
|
output.Content.SetHtmlContent(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
", vcthClass.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RazorCodeDocument CreateDocument(string content)
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
stream.Seek(0L, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var source = RazorSourceDocument.ReadFrom(stream, "test.cshtml");
|
||||||
|
return RazorCodeDocument.Create(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RazorEngine CreateEngine(params TagHelperDescriptor[] tagHelpers)
|
||||||
|
{
|
||||||
|
return RazorEngine.Create(b =>
|
||||||
|
{
|
||||||
|
b.Features.Add(new MvcViewDocumentClassifierPass());
|
||||||
|
|
||||||
|
b.Features.Add(new TagHelperFeature(tagHelpers));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DocumentIRNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < engine.Phases.Count; i++)
|
||||||
|
{
|
||||||
|
var phase = engine.Phases[i];
|
||||||
|
phase.Execute(codeDocument);
|
||||||
|
|
||||||
|
if (phase is IRazorIRPhase)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeDocument.GetIRDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassDeclarationIRNode FindClassNode(RazorIRNode node)
|
||||||
|
{
|
||||||
|
var visitor = new ClassDeclarationNodeVisitor();
|
||||||
|
visitor.Visit(node);
|
||||||
|
return visitor.Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TagHelperIRNode FindTagHelperNode(RazorIRNode node)
|
||||||
|
{
|
||||||
|
var visitor = new TagHelperNodeVisitor();
|
||||||
|
visitor.Visit(node);
|
||||||
|
return visitor.Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCSharpContent(RazorIRNode node)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = node.Children[i] as CSharpTokenIRNode;
|
||||||
|
builder.Append(child.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClassDeclarationNodeVisitor : RazorIRNodeWalker
|
||||||
|
{
|
||||||
|
public ClassDeclarationIRNode Node { get; set; }
|
||||||
|
|
||||||
|
public override void VisitClass(ClassDeclarationIRNode node)
|
||||||
|
{
|
||||||
|
Node = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TagHelperNodeVisitor : RazorIRNodeWalker
|
||||||
|
{
|
||||||
|
public TagHelperIRNode Node { get; set; }
|
||||||
|
|
||||||
|
public override void VisitTagHelper(TagHelperIRNode node)
|
||||||
|
{
|
||||||
|
Node = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TagHelperFeature : ITagHelperFeature
|
||||||
|
{
|
||||||
|
public TagHelperFeature(TagHelperDescriptor[] tagHelpers)
|
||||||
|
{
|
||||||
|
Resolver = new TagHelperDescriptorResolver(tagHelpers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RazorEngine Engine { get; set; }
|
||||||
|
|
||||||
|
public ITagHelperDescriptorResolver Resolver { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TagHelperDescriptorResolver : ITagHelperDescriptorResolver
|
||||||
|
{
|
||||||
|
public TagHelperDescriptorResolver(TagHelperDescriptor[] tagHelpers)
|
||||||
|
{
|
||||||
|
TagHelpers = tagHelpers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagHelperDescriptor[] TagHelpers { get; }
|
||||||
|
|
||||||
|
public IEnumerable<TagHelperDescriptor> Resolve(ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
return TagHelpers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue