Add unit tests for ModelExpressionPass

Add unit tests for @inject
This commit is contained in:
Ryan Nowak 2017-01-30 16:15:29 -08:00
parent b79f8384b0
commit 705369ee51
14 changed files with 604 additions and 502 deletions

View File

@ -1,32 +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.
namespace Microsoft.AspNetCore.Mvc.Razor
{
/// <summary>
/// Contains information for the tag helper attribute code
/// generation process.
/// </summary>
public class GeneratedTagHelperAttributeContext
{
/// <summary>
/// Name of the model expression type.
/// </summary>
public string ModelExpressionTypeName { get; set; }
/// <summary>
/// Name the method to create <c>ModelExpression</c>s.
/// </summary>
public string CreateModelExpressionMethodName { get; set; }
/// <summary>
/// Gets or sets the name of the <c>IModelExpressionProvider</c>.
/// </summary>
public string ModelExpressionProviderPropertyName { get; set; }
/// <summary>
/// Gets or sets the property name of the <c>ViewDataDictionary</c>.
/// </summary>
public string ViewDataPropertyName { get; set; }
}
}

View File

@ -1,54 +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.
namespace Microsoft.AspNetCore.Mvc.Razor.Host.Internal
{
/// <summary>
/// Contains necessary information for the view component tag helper code generation process.
/// </summary>
public class GeneratedViewComponentTagHelperContext
{
/// <summary>
/// Instantiates a new instance of the <see cref="GeneratedViewComponentTagHelperContext"/> with default values.
/// </summary>
public GeneratedViewComponentTagHelperContext()
{
ContextualizeMethodName = "Contextualize";
InvokeAsyncMethodName = "InvokeAsync";
IViewComponentHelperTypeName = "Microsoft.AspNetCore.Mvc.IViewComponentHelper";
IViewContextAwareTypeName = "Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware";
ViewContextAttributeTypeName = "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute";
ViewContextTypeName = "Microsoft.AspNetCore.Mvc.Rendering.ViewContext";
}
/// <summary>
/// Name of the Contextualize method called by an instance of the IViewContextAware type.
/// </summary>
public string ContextualizeMethodName { get; set; }
/// <summary>
/// Name of the InvokeAsync method called by an IViewComponentHelper.
/// </summary>
public string InvokeAsyncMethodName { get; set; }
/// <summary>
/// Name of the IViewComponentHelper type used to invoke view components.
/// </summary>
public string IViewComponentHelperTypeName { get; set; }
/// <summary>
/// Name of the IViewContextAware type used to contextualize the view context.
/// </summary>
public string IViewContextAwareTypeName { get; set; }
/// <summary>
/// Name of the ViewContext type for view execution.
/// </summary>
public string ViewContextTypeName { get; set; }
/// <summary>
/// Name of the ViewContextAttribute type.
/// </summary>
public string ViewContextAttributeTypeName { get; set; }
}
}

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
return builder;
}
private class Pass : IRazorIRPass
internal class Pass : IRazorIRPass
{
public RazorEngine Engine { get; set; }
@ -31,18 +31,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
var visitor = new Visitor();
visitor.Visit(irDocument);
for (var i = 0; i < visitor.Directives.Count; i++)
var properties = new HashSet<string>(StringComparer.Ordinal);
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
{
var directive = visitor.Directives[i];
var typeName = directive.Tokens.ElementAt(0).Content;;
var memberName = directive.Tokens.ElementAt(1).Content;
var modelType = "dynamic";
if (visitor.ModelType.Count > 0)
var tokens = directive.Tokens.ToArray();
if (tokens.Length < 2)
{
modelType = visitor.ModelType.Last().Tokens.First().Content;
continue;
}
var typeName = tokens[0].Content;
var memberName = tokens[1].Content;
if (!properties.Add(memberName))
{
continue;
}
var modelType = ModelDirective.GetModelType(irDocument);
typeName = typeName.Replace("<TModel>", "<" + modelType + ">");
var member = new CSharpStatementIRNode()

View File

@ -1,6 +1,7 @@
// 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.Linq;
using Microsoft.AspNetCore.Razor.Evolution;
@ -19,6 +20,35 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
return builder;
}
public static string GetModelType(DocumentIRNode document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
var visitor = new Visitor();
visitor.Visit(document);
return GetModelType(visitor);
}
private static string GetModelType(Visitor visitor)
{
for (var i = visitor.ModelDirectives.Count - 1; i >= 0; i--)
{
var directive = visitor.ModelDirectives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length >= 1)
{
return tokens[0].Content;
}
}
return "dynamic";
}
private class Pass : IRazorIRPass
{
public RazorEngine Engine { get; set; }
@ -31,13 +61,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
var visitor = new Visitor();
visitor.Visit(irDocument);
string modelType = "dynamic";
if (visitor.Directives.Count == 1)
var modelType = GetModelType(visitor);
var baseType = visitor.Class.BaseType;
for (var i = visitor.InheritsDirectives.Count - 1; i >= 0; i--)
{
modelType = visitor.Directives.Last().Tokens.First().Content;
var directive = visitor.InheritsDirectives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length >= 1)
{
baseType = tokens[0].Content;
break;
}
}
visitor.Class.BaseType = visitor.Class.BaseType.Replace("<TModel>", "<" + modelType + ">");
visitor.Class.BaseType = baseType.Replace("<TModel>", "<" + modelType + ">");
return irDocument;
}
@ -47,7 +85,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
{
public ClassDeclarationIRNode Class { get; private set; }
public IList<DirectiveIRNode> Directives { get; } = new List<DirectiveIRNode>();
public IList<DirectiveIRNode> InheritsDirectives { get; } = new List<DirectiveIRNode>();
public IList<DirectiveIRNode> ModelDirectives { get; } = new List<DirectiveIRNode>();
public override void VisitClass(ClassDeclarationIRNode node)
{
@ -63,7 +103,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
{
if (node.Descriptor == Directive)
{
Directives.Add(node);
ModelDirectives.Add(node);
}
else if (node.Descriptor.Name == "inherits")
{
InheritsDirectives.Add(node);
}
}
}

View File

@ -38,9 +38,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
Content = "ModelExpressionProvider.CreateModelExpression(ViewData, __model => ",
});
if (node.Children.Count == 1 && node.Children[0] is HtmlContentIRNode)
{
// A 'simple' expression will look like __model => __model.Foo
//
// Note that the fact we're looking for HTML here is based on a bug.
// https://github.com/aspnet/Razor/issues/963
var original = ((HtmlContentIRNode)node.Children[0]);
builder.Add(new CSharpTokenIRNode()
@ -58,7 +61,32 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
{
for (var i = 0; i < node.Children.Count; i++)
{
builder.Add(node.Children[i]);
var nestedExpression = node.Children[i] as CSharpExpressionIRNode;
if (nestedExpression != null)
{
for (var j = 0; j < nestedExpression.Children.Count; j++)
{
var cSharpToken = nestedExpression.Children[j] as CSharpTokenIRNode;
if (cSharpToken != null)
{
builder.Add(cSharpToken);
}
}
continue;
}
// Note that the fact we're looking for HTML here is based on a bug.
// https://github.com/aspnet/Razor/issues/963
var html = node.Children[i] as HtmlContentIRNode;
if (html != null)
{
builder.Add(new CSharpTokenIRNode()
{
Content = html.Content,
Source = html.Source,
});
}
}
}

View File

@ -90,5 +90,9 @@
{
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.UsingChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
"Kind": "Removal"
},
{
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.GeneratedTagHelperAttributeContext",
"Kind": "Removal"
}
]

View File

@ -90,5 +90,9 @@
{
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Directives.UsingChunkMerger : Microsoft.AspNetCore.Mvc.Razor.Directives.IChunkMerger",
"Kind": "Removal"
},
{
"OldTypeId": "public class Microsoft.AspNetCore.Mvc.Razor.GeneratedTagHelperAttributeContext",
"Kind": "Removal"
}
]

View File

@ -1,174 +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 InjectChunkMergerTest
{
#if OLD_RAZOR
[Theory]
[InlineData("MyApp.TestHelper<TModel>", "MyApp.TestHelper<Person>")]
[InlineData("TestBaseType", "TestBaseType")]
public void Visit_UpdatesTModelTokenToMatchModelType(string typeName, string expectedValue)
{
// Arrange
var chunk = new InjectChunk(typeName, "TestHelper");
var merger = new InjectChunkMerger("Person");
// Act
merger.VisitChunk(chunk);
// Assert
Assert.Equal(expectedValue, chunk.TypeName);
Assert.Equal("TestHelper", chunk.MemberName);
}
[Fact]
public void Merge_AddsChunkIfChunkWithMatchingPropertyNameWasNotVisitedInChunkTree()
{
// Arrange
var expectedType = "MyApp.MyHelperType";
var expectedProperty = "MyHelper";
var merger = new InjectChunkMerger("dynamic");
var chunkTree = new ChunkTree();
var inheritedChunks = new[]
{
new InjectChunk(expectedType, expectedProperty)
};
// Act
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
// Assert
var chunk = Assert.Single(chunkTree.Children);
var injectChunk = Assert.IsType<InjectChunk>(chunk);
Assert.Equal(expectedType, injectChunk.TypeName);
Assert.Equal(expectedProperty, injectChunk.MemberName);
}
[Fact]
public void Merge_IgnoresChunkIfChunkWithMatchingPropertyNameWasVisitedInChunkTree()
{
// Arrange
var merger = new InjectChunkMerger("dynamic");
var chunkTree = new ChunkTree();
var inheritedChunks = new[]
{
new InjectChunk("MyTypeB", "MyProperty")
};
// Act
merger.VisitChunk(new InjectChunk("MyTypeA", "MyProperty"));
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
// Assert
Assert.Empty(chunkTree.Children);
}
[Fact]
public void Merge_MatchesPropertyNameInCaseSensitiveManner()
{
// Arrange
var merger = new InjectChunkMerger("dynamic");
var chunkTree = new ChunkTree();
var inheritedChunks = new[]
{
new InjectChunk("MyTypeB", "different-property"),
new InjectChunk("MyType", "myproperty"),
};
// Act
merger.VisitChunk(new InjectChunk("MyType", "MyProperty"));
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
// Assert
Assert.Equal(2, chunkTree.Children.Count);
var injectChunk = Assert.IsType<InjectChunk>(chunkTree.Children[0]);
Assert.Equal("MyType", injectChunk.TypeName);
Assert.Equal("myproperty", injectChunk.MemberName);
injectChunk = Assert.IsType<InjectChunk>(chunkTree.Children[1]);
Assert.Equal("MyTypeB", injectChunk.TypeName);
Assert.Equal("different-property", injectChunk.MemberName);
}
[Fact]
public void Merge_ResolvesModelNameInTypesWithTModelToken()
{
// Arrange
var merger = new InjectChunkMerger("dynamic");
var chunkTree = new ChunkTree();
var inheritedChunks = new[]
{
new InjectChunk("MyHelper<TModel>", "MyProperty")
};
// Act
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
// Assert
var chunk = Assert.Single(chunkTree.Children);
var injectChunk = Assert.IsType<InjectChunk>(chunk);
Assert.Equal("MyHelper<dynamic>", injectChunk.TypeName);
Assert.Equal("MyProperty", injectChunk.MemberName);
}
[Fact]
public void Merge_ReplacesTModelTokensWithModel()
{
// Arrange
var merger = new InjectChunkMerger("MyTestModel2");
var chunkTree = new ChunkTree();
var inheritedChunks = new[]
{
new InjectChunk("MyHelper<TModel>", "MyProperty")
};
// Act
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
// Assert
var chunk = Assert.Single(chunkTree.Children);
var injectChunk = Assert.IsType<InjectChunk>(chunk);
Assert.Equal("MyHelper<MyTestModel2>", injectChunk.TypeName);
Assert.Equal("MyProperty", injectChunk.MemberName);
}
[Fact]
public void Merge_UsesTheLastInjectChunkOfAPropertyName()
{
// Arrange
var merger = new InjectChunkMerger("dynamic");
var chunkTree = new ChunkTree();
var inheritedChunks = new Chunk[]
{
new LiteralChunk(),
new InjectChunk("SomeOtherType", "Property"),
new InjectChunk("DifferentPropertyType", "DifferentProperty"),
new InjectChunk("SomeType", "Property"),
};
// Act
merger.MergeInheritedChunks(chunkTree, inheritedChunks);
// Assert
Assert.Collection(chunkTree.Children,
chunk =>
{
var injectChunk = Assert.IsType<InjectChunk>(chunk);
Assert.Equal("SomeType", injectChunk.TypeName);
Assert.Equal("Property", injectChunk.MemberName);
},
chunk =>
{
var injectChunk = Assert.IsType<InjectChunk>(chunk);
Assert.Equal("DifferentPropertyType", injectChunk.TypeName);
Assert.Equal("DifferentProperty", injectChunk.MemberName);
});
}
#endif
}
}

View File

@ -1,163 +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.Mvc.Razor.Directives;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Chunks;
using Microsoft.AspNetCore.Razor.Chunks.Generators;
using Microsoft.AspNetCore.Razor.CodeGenerators;
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor
{
public class InjectChunkVisitorTest
{
#if OLD_RAZOR
[Fact]
public void Visit_IgnoresNonInjectChunks()
{
// Arrange
var writer = new CSharpCodeWriter();
var context = CreateContext();
var visitor = new InjectChunkVisitor(writer, context, "ActivateAttribute");
// Act
visitor.Accept(new Chunk[]
{
new LiteralChunk(),
new CodeAttributeChunk()
});
var code = writer.GenerateCode();
// Assert
Assert.Empty(code);
}
[Fact]
public void Visit_GeneratesProperties_ForInjectChunks()
{
// Arrange
var expected =
@"[ActivateAttribute]
public MyType1 MyPropertyName1 { get; private set; }
[ActivateAttribute]
public MyType2 @MyPropertyName2 { get; private set; }
";
var writer = new CSharpCodeWriter();
var context = CreateContext();
var visitor = new InjectChunkVisitor(writer, context, "ActivateAttribute");
var factory = SpanFactory.CreateCsHtml();
var node = (Span)factory.Code("Some code")
.As(new InjectParameterGenerator("MyType", "MyPropertyName"));
// Act
visitor.Accept(new Chunk[]
{
new LiteralChunk(),
new InjectChunk("MyType1", "MyPropertyName1") { Association = node },
new InjectChunk("MyType2", "@MyPropertyName2") { Association = node }
});
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
[Fact]
public void Visit_WithDesignTimeHost_GeneratesPropertiesAndLinePragmas_ForInjectChunks()
{
// Arrange
var expected = string.Join(Environment.NewLine,
"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]",
"public",
@"#line 1 """"",
"MyType1 MyPropertyName1",
"",
"#line default",
"#line hidden",
"{ get; private set; }",
"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]",
"public",
@"#line 1 """"",
"MyType2 @MyPropertyName2",
"",
"#line default",
"#line hidden",
"{ get; private set; }",
"");
var writer = new CSharpCodeWriter();
var context = CreateContext();
context.Host.DesignTimeMode = true;
var visitor = new InjectChunkVisitor(writer, context, "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute");
var factory = SpanFactory.CreateCsHtml();
var node = (Span)factory.Code("Some code")
.As(new InjectParameterGenerator("MyType", "MyPropertyName"));
// Act
visitor.Accept(new Chunk[]
{
new LiteralChunk(),
new InjectChunk("MyType1", "MyPropertyName1") { Association = node },
new InjectChunk("MyType2", "@MyPropertyName2") { Association = node }
});
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
[Fact]
public void Visit_WithDesignTimeHost_GeneratesPropertiesAndLinePragmas_ForPartialInjectChunks()
{
// Arrange
var expected = @"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public
#line 1 """"
MyType1
#line default
#line hidden
{ get; private set; }
";
var writer = new CSharpCodeWriter();
var context = CreateContext();
context.Host.DesignTimeMode = true;
var visitor = new InjectChunkVisitor(writer, context, "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute");
var factory = SpanFactory.CreateCsHtml();
var node = (Span)factory.Code("Some code")
.As(new InjectParameterGenerator("MyType", "MyPropertyName"));
// Act
visitor.Accept(new Chunk[]
{
new LiteralChunk(),
new InjectChunk("MyType1", string.Empty) { Association = node },
});
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
private static CodeGeneratorContext CreateContext()
{
var chunkTreeCache = new DefaultChunkTreeCache(new TestFileProvider());
return new CodeGeneratorContext(
new ChunkGeneratorContext(
new MvcRazorHost(chunkTreeCache, new TagHelperDescriptorResolver(designTime: false)),
"MyClass",
"MyNamespace",
string.Empty,
shouldGenerateLinePragmas: true),
new ErrorSink());
}
#endif
}
}

View File

@ -0,0 +1,241 @@
// 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.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 InjectDirectiveTest
{
[Fact]
public void InjectDirectivePass_Execute_DefinesProperty()
{
// Arrange
var codeDocument = CreateDocument(@"
@inject PropertyType PropertyName
");
var engine = CreateEngine();
var pass = new InjectDirective.Pass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var @class = FindClassNode(irDocument);
Assert.NotNull(@class);
Assert.Equal(2, @class.Children.Count);
var statement = Assert.IsType<CSharpStatementIRNode>(@class.Children[1]);
Assert.Equal(
"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]" + Environment.NewLine +
"public PropertyType PropertyName { get; private set; }",
statement.Content);
}
[Fact]
public void InjectDirectivePass_Execute_DedupesPropertiesByName()
{
// Arrange
var codeDocument = CreateDocument(@"
@inject PropertyType PropertyName
@inject PropertyType2 PropertyName
");
var engine = CreateEngine();
var pass = new InjectDirective.Pass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var @class = FindClassNode(irDocument);
Assert.NotNull(@class);
Assert.Equal(2, @class.Children.Count);
var statement = Assert.IsType<CSharpStatementIRNode>(@class.Children[1]);
Assert.Equal(
"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]" + Environment.NewLine +
"public PropertyType2 PropertyName { get; private set; }",
statement.Content);
}
[Fact]
public void InjectDirectivePass_Execute_ExpandsTModel_WithDynamic()
{
// Arrange
var codeDocument = CreateDocument(@"
@inject PropertyType<TModel> PropertyName
");
var engine = CreateEngine();
var pass = new InjectDirective.Pass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var @class = FindClassNode(irDocument);
Assert.NotNull(@class);
Assert.Equal(2, @class.Children.Count);
var statement = Assert.IsType<CSharpStatementIRNode>(@class.Children[1]);
Assert.Equal(
"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]" + Environment.NewLine +
"public PropertyType<dynamic> PropertyName { get; private set; }",
statement.Content);
}
[Fact]
public void InjectDirectivePass_Execute_ExpandsTModel_WithModelTypeFirst()
{
// Arrange
var codeDocument = CreateDocument(@"
@model ModelType
@inject PropertyType<TModel> PropertyName
");
var engine = CreateEngine();
var pass = new InjectDirective.Pass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var @class = FindClassNode(irDocument);
Assert.NotNull(@class);
Assert.Equal(2, @class.Children.Count);
var statement = Assert.IsType<CSharpStatementIRNode>(@class.Children[1]);
Assert.Equal(
"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]" + Environment.NewLine +
"public PropertyType<ModelType> PropertyName { get; private set; }",
statement.Content);
}
[Fact]
public void InjectDirectivePass_Execute_ExpandsTModel_WithModelType()
{
// Arrange
var codeDocument = CreateDocument(@"
@inject PropertyType<TModel> PropertyName
@model ModelType
");
var engine = CreateEngine();
var pass = new InjectDirective.Pass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var @class = FindClassNode(irDocument);
Assert.NotNull(@class);
Assert.Equal(2, @class.Children.Count);
var statement = Assert.IsType<CSharpStatementIRNode>(@class.Children[1]);
Assert.Equal(
"[Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]" + Environment.NewLine +
"public PropertyType<ModelType> PropertyName { get; private set; }",
statement.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 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 InjectDirective.Pass here so we can run it on demand.
b.AddDirective(InjectDirective.Directive);
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;
}
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Razor.Host.Internal;
using Microsoft.AspNetCore.Razor.Chunks;
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
using Moq;

View File

@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Razor.Host.Internal;
using Microsoft.AspNetCore.Razor.Chunks;
using Microsoft.AspNetCore.Razor.CodeGenerators;
using Xunit;

View File

@ -0,0 +1,258 @@
// 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 MvcTagHelperAttributeValueCodeRendererTest
{
[Fact]
public void ModelExpressionPass_NonModelExpressionProperty_Ignored()
{
// 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 ModelExpressionPass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var tagHelper = FindTagHelperNode(irDocument);
var setProperty = tagHelper.Children.OfType<SetTagHelperPropertyIRNode>().Single();
var child = Assert.IsType<HtmlContentIRNode>(Assert.Single(setProperty.Children));
Assert.Equal("17", child.Content);
}
[Fact]
public void ModelExpressionPass_ModelExpressionProperty_SimpleExpression()
{
// Arrange
var codeDocument = CreateDocument(@"
@addTagHelper TestTagHelper, TestAssembly
<p foo=""Bar"">");
var tagHelpers = new[]
{
new TagHelperDescriptor()
{
AssemblyName = "TestAssembly",
TypeName = "TestTagHelper",
TagName = "p",
Attributes = new TagHelperAttributeDescriptor[]
{
new TagHelperAttributeDescriptor()
{
TypeName = typeof(ModelExpression).FullName,
Name = "Foo",
}
}
}
};
var engine = CreateEngine(tagHelpers);
var pass = new ModelExpressionPass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var tagHelper = FindTagHelperNode(irDocument);
var setProperty = tagHelper.Children.OfType<SetTagHelperPropertyIRNode>().Single();
var expression = Assert.IsType<CSharpExpressionIRNode>(Assert.Single(setProperty.Children));
Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => __model.Bar)", GetCSharpContent(expression));
var originalNode = Assert.IsType<CSharpTokenIRNode>(expression.Children[2]);
Assert.Equal("Bar", originalNode.Content);
Assert.Equal(new SourceSpan("test.cshtml", 53, 2, 8, 3), originalNode.Source.Value);
}
[Fact]
public void ModelExpressionPass_ModelExpressionProperty_ComplexExpression()
{
// Arrange
var codeDocument = CreateDocument(@"
@addTagHelper TestTagHelper, TestAssembly
<p foo=""@Bar"">");
var tagHelpers = new[]
{
new TagHelperDescriptor()
{
AssemblyName = "TestAssembly",
TypeName = "TestTagHelper",
TagName = "p",
Attributes = new TagHelperAttributeDescriptor[]
{
new TagHelperAttributeDescriptor()
{
TypeName = typeof(ModelExpression).FullName,
Name = "Foo",
}
}
}
};
var engine = CreateEngine(tagHelpers);
var pass = new ModelExpressionPass()
{
Engine = engine,
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var tagHelper = FindTagHelperNode(irDocument);
var setProperty = tagHelper.Children.OfType<SetTagHelperPropertyIRNode>().Single();
var expression = Assert.IsType<CSharpExpressionIRNode>(Assert.Single(setProperty.Children));
Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => Bar)", GetCSharpContent(expression));
var originalNode = Assert.IsType<CSharpTokenIRNode>(expression.Children[1]);
Assert.Equal("Bar", originalNode.Content);
Assert.Equal(new SourceSpan("test.cshtml", 54, 2, 9, 3), originalNode.Source.Value);
}
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 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 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 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;
}
}
}
}

View File

@ -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 Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Chunks.Generators;
using Microsoft.AspNetCore.Razor.CodeGenerators;
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor
{
public class MvcTagHelperAttributeValueCodeRendererTest
{
#if OLD_RAZOR
[Theory]
[InlineData("SomeType", "SomeType", "Provider.SomeMethod(ViewData, __model => __model.MyValue)")]
[InlineData("SomeType", "SomeType2", "MyValue")]
public void RenderAttributeValue_RendersModelExpressionsCorrectly(
string modelExpressionType,
string propertyType,
string expectedValue)
{
// Arrange
var renderer = new MvcTagHelperAttributeValueCodeRenderer(
new GeneratedTagHelperAttributeContext
{
ModelExpressionTypeName = modelExpressionType,
CreateModelExpressionMethodName = "SomeMethod",
ModelExpressionProviderPropertyName = "Provider",
ViewDataPropertyName = "ViewData"
});
var attributeDescriptor = new TagHelperAttributeDescriptor
{
Name = "MyAttribute",
PropertyName = "SomeProperty",
TypeName = propertyType,
};
var writer = new CSharpCodeWriter();
var generatorContext = new ChunkGeneratorContext(
host: null,
className: string.Empty,
rootNamespace: string.Empty,
sourceFile: string.Empty,
shouldGenerateLinePragmas: true);
var errorSink = new ErrorSink();
var context = new CodeGeneratorContext(generatorContext, errorSink);
// Act
renderer.RenderAttributeValue(attributeDescriptor, writer, context,
(codeWriter) =>
{
codeWriter.Write("MyValue");
},
complexValue: false);
// Assert
Assert.Equal(expectedValue, writer.GenerateCode());
}
#endif
}
}