From 2ad175b687cf13aacc41e53aa5c8d00833ed9a1b Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 24 Jun 2014 12:09:29 -0700 Subject: [PATCH] Add @model specific tests to validate output. #568 --- .../MvcCSharpCodeBuilder.cs | 10 +- ...Microsoft.AspNet.Mvc.Razor.Host.Test.kproj | 3 + .../ModelChunkVisitorTest.cs | 176 ++++++++++++++++++ .../TestFiles/Input/Model.cshtml | 1 + .../TestFiles/Output/Model.cs | 32 ++++ 5 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/Model.cshtml create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs index c7a510feda..fe4e3082cb 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs @@ -19,16 +19,20 @@ namespace Microsoft.AspNet.Mvc.Razor protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer) { - var chunks = Context.CodeTreeBuilder.CodeTree.Chunks; + var modelChunks = Context.CodeTreeBuilder.CodeTree.Chunks.OfType(); // If there were any model chunks then we need to modify the class declaration signature. - if (chunks.OfType().Any()) + if (modelChunks.Any()) { writer.Write(string.Format(CultureInfo.CurrentCulture, "public class {0} : ", Context.ClassName)); + // Grab the last model chunk so it gets intellisense. + // NOTE: If there's more than 1 model chunk there will be a Razor error BUT we want intellisense to show up + // on the current model chunk that the user is typing. + var lastModelChunk = modelChunks.Last(); var modelVisitor = new ModelChunkVisitor(writer, Context); // This generates the base class signature - modelVisitor.Accept(chunks); + modelVisitor.Accept(lastModelChunk); writer.WriteLine(); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Microsoft.AspNet.Mvc.Razor.Host.Test.kproj b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Microsoft.AspNet.Mvc.Razor.Host.Test.kproj index 8b7af35e08..9b048f99ba 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Microsoft.AspNet.Mvc.Razor.Host.Test.kproj +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Microsoft.AspNet.Mvc.Razor.Host.Test.kproj @@ -24,9 +24,11 @@ + + @@ -35,6 +37,7 @@ + diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs new file mode 100644 index 0000000000..76867afa6c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.IO; +using Microsoft.AspNet.Razor; +using Microsoft.AspNet.Razor.Generator; +using Microsoft.AspNet.Razor.Generator.Compiler; +using Microsoft.AspNet.Razor.Generator.Compiler.CSharp; +using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.Text; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Razor +{ + public class ModelChunkVisitorTest + { + [Fact] + public void Visit_IgnoresNonModelChunks() + { + // Arrange + var writer = new CSharpCodeWriter(); + var context = CreateContext(); + + var visitor = new ModelChunkVisitor(writer, context); + + // Act + visitor.Accept(new Chunk[] + { + new LiteralChunk(), + new CodeAttributeChunk() + }); + var code = writer.GenerateCode(); + + // Assert + Assert.Empty(code); + } + + [Fact] + public void Visit_GeneratesBaseClass_ForModelChunks() + { + // Arrange + var expected = +"MyBase<" + Environment.NewLine + +"#line 1 \"\"" + Environment.NewLine + +"My_Generic.After.Periods" + Environment.NewLine + +Environment.NewLine + +"#line default" + Environment.NewLine + +"#line hidden" + Environment.NewLine + +">"; + var writer = new CSharpCodeWriter(); + var context = CreateContext(); + + var visitor = new ModelChunkVisitor(writer, context); + var factory = SpanFactory.CreateCsHtml(); + var node = (Span)factory.Code("Some code") + .As(new ModelCodeGenerator("MyBase", "MyGeneric")); + + // Act + visitor.Accept(new Chunk[] + { + new LiteralChunk(), + new ModelChunk("MyBase", "My_Generic.After.Periods") { Association = node } + }); + var code = writer.GenerateCode(); + + // Assert + Assert.Equal(expected, code); + } + + [Fact] + public void Visit_WithDesignTimeHost_GeneratesBaseClass_ForModelChunks() + { + // Arrange + var expected = +"MyBase<" + Environment.NewLine + +"#line 1 \"\"" + Environment.NewLine + +"My_Generic.After.Periods" + Environment.NewLine + +Environment.NewLine + +"#line default" + Environment.NewLine + +"#line hidden" + Environment.NewLine + +">"; + var writer = new CSharpCodeWriter(); + var context = CreateContext(); + context.Host.DesignTimeMode = true; + + var visitor = new ModelChunkVisitor(writer, context); + var factory = SpanFactory.CreateCsHtml(); + var node = (Span)factory.Code("Some code") + .As(new ModelCodeGenerator("MyType", "MyPropertyName")); + + // Act + visitor.Accept(new Chunk[] + { + new LiteralChunk(), + new ModelChunk("MyBase", "My_Generic.After.Periods") { Association = node } + }); + var code = writer.GenerateCode(); + + // Assert + Assert.Equal(expected, code); + } + + [Fact] + public void ModelVisitor_GeneratesCorrectLineMappings() + { + // Arrange + var host = new MvcRazorHost("RazorView") + { + DesignTimeMode = true + }; + host.NamespaceImports.Clear(); + var engine = new RazorTemplateEngine(host); + var source = ReadResource("Model.cshtml"); + var expectedCode = ReadResource("Model.cs"); + var expectedLineMappings = new List + { + BuildLineMapping(7, 0, 7, 126, 6, 7, 30), + }; + + // Act + GeneratorResults results; + using (var buffer = new StringTextBuffer(source)) + { + results = engine.GenerateCode(buffer); + } + + // Assert + Assert.True(results.Success); + Assert.Equal(expectedCode, results.GeneratedCode); + Assert.Empty(results.ParserErrors); + Assert.Equal(expectedLineMappings, results.DesignTimeLineMappings); + } + + private string ReadResource(string resourceName) + { + var assembly = typeof(ModelChunkVisitorTest).Assembly; + + using (var stream = assembly.GetManifestResourceStream(resourceName)) + using (var streamReader = new StreamReader(stream)) + { + return streamReader.ReadToEnd(); + } + } + + private static CodeGeneratorContext CreateContext() + { + return CodeGeneratorContext.Create(new MvcRazorHost("RazorView"), + "MyClass", + "MyNamespace", + string.Empty, + shouldGenerateLinePragmas: true); + } + + private static LineMapping BuildLineMapping(int documentAbsoluteIndex, + int documentLineIndex, + int documentCharacterIndex, + int generatedAbsoluteIndex, + int generatedLineIndex, + int generatedCharacterIndex, + int contentLength) + { + var documentLocation = new SourceLocation(documentAbsoluteIndex, + documentLineIndex, + documentCharacterIndex); + var generatedLocation = new SourceLocation(generatedAbsoluteIndex, + generatedLineIndex, + generatedCharacterIndex); + + return new LineMapping( + documentLocation: new MappingLocation(documentLocation, contentLength), + generatedLocation: new MappingLocation(generatedLocation, contentLength)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/Model.cshtml b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/Model.cshtml new file mode 100644 index 0000000000..4b73b2dc53 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/Model.cshtml @@ -0,0 +1 @@ +@model System.Collections.IEnumerable diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs new file mode 100644 index 0000000000..73f1cdc53b --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Model.cs @@ -0,0 +1,32 @@ +namespace Razor +{ + using System.Threading.Tasks; + + public class __CompiledTemplate : RazorView< +#line 1 "" + System.Collections.IEnumerable + +#line default +#line hidden + > + { + private static object @__o; + private void @__RazorDesignTimeHelpers__() + { + #pragma warning disable 219 + #pragma warning restore 219 + } + #line hidden + + #line hidden + public __CompiledTemplate() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + } + #pragma warning restore 1998 + } +}