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";
|
||||
}
|
||||
|
||||
private class Pass : IRazorIRPass
|
||||
internal class Pass : IRazorIRPass
|
||||
{
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
|||
writer.WriteAutoPropertyDeclaration(
|
||||
"public",
|
||||
$"global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext",
|
||||
"_context");
|
||||
"ViewContext");
|
||||
|
||||
var indexerAttributes = descriptor.Attributes.Where(a => a.IsIndexer);
|
||||
|
||||
|
|
@ -187,19 +187,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
|||
writer.WriteInstanceMethodInvocation(
|
||||
$"(_helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?",
|
||||
"Contextualize",
|
||||
new[] { "_context" });
|
||||
new[] { "ViewContext" });
|
||||
|
||||
var methodParameters = GetMethodParameters(descriptor);
|
||||
var viewContentVariable = "viewContent";
|
||||
var contentVariable = "content";
|
||||
writer.Write("var ")
|
||||
.WriteStartAssignment(viewContentVariable)
|
||||
.WriteStartAssignment(contentVariable)
|
||||
.WriteInstanceMethodInvocation($"await _helper", "InvokeAsync", methodParameters);
|
||||
writer.WriteStartAssignment($"{outputVariable}.TagName")
|
||||
.WriteLine("null;");
|
||||
writer.WriteInstanceMethodInvocation(
|
||||
$"{outputVariable}.Content",
|
||||
"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 joinedPropertyNames = string.Join(", ", propertyNames);
|
||||
var parametersString = $" new {{ { joinedPropertyNames } }}";
|
||||
var parametersString = $"new {{ { joinedPropertyNames } }}";
|
||||
|
||||
var viewComponentName = descriptor.PropertyBag[
|
||||
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