403 lines
16 KiB
C#
403 lines
16 KiB
C#
// 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.IO;
|
|
using Microsoft.AspNet.Mvc.Razor.Directives;
|
|
using Microsoft.AspNet.Mvc.Razor.Internal;
|
|
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;
|
|
using Microsoft.Framework.Internal;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNet.Mvc.Razor
|
|
{
|
|
public class MvcRazorHostTest
|
|
{
|
|
[Theory]
|
|
[InlineData("//")]
|
|
[InlineData("C:/")]
|
|
[InlineData(@"\\")]
|
|
[InlineData(@"C:\")]
|
|
public void DecorateRazorParser_DesignTimeRazorPathNormalizer_NormalizesChunkInheritanceUtilityPaths(
|
|
string rootPrefix)
|
|
{
|
|
// Arrange
|
|
var rootedAppPath = $"{rootPrefix}SomeComputer/Location/Project/";
|
|
var rootedFilePath = $"{rootPrefix}SomeComputer/Location/Project/src/file.cshtml";
|
|
var host = new MvcRazorHost(
|
|
codeTreeCache: null,
|
|
pathNormalizer: new DesignTimeRazorPathNormalizer(rootedAppPath));
|
|
var parser = new RazorParser(
|
|
host.CodeLanguage.CreateCodeParser(),
|
|
host.CreateMarkupParser(),
|
|
tagHelperDescriptorResolver: null);
|
|
var chunkInheritanceUtility = new PathValidatingChunkInheritanceUtility(host);
|
|
host.ChunkInheritanceUtility = chunkInheritanceUtility;
|
|
|
|
// Act
|
|
host.DecorateRazorParser(parser, rootedFilePath);
|
|
|
|
// Assert
|
|
Assert.Equal("src/file.cshtml", chunkInheritanceUtility.InheritedCodeTreePagePath, StringComparer.Ordinal);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("//")]
|
|
[InlineData("C:/")]
|
|
[InlineData(@"\\")]
|
|
[InlineData(@"C:\")]
|
|
public void DecorateCodeBuilder_DesignTimeRazorPathNormalizer_NormalizesChunkInheritanceUtilityPaths(
|
|
string rootPrefix)
|
|
{
|
|
// Arrange
|
|
var rootedAppPath = $"{rootPrefix}SomeComputer/Location/Project/";
|
|
var rootedFilePath = $"{rootPrefix}SomeComputer/Location/Project/src/file.cshtml";
|
|
var host = new MvcRazorHost(
|
|
codeTreeCache: null,
|
|
pathNormalizer: new DesignTimeRazorPathNormalizer(rootedAppPath));
|
|
var chunkInheritanceUtility = new PathValidatingChunkInheritanceUtility(host);
|
|
var codeBuilderContext = new CodeBuilderContext(
|
|
new CodeGeneratorContext(
|
|
host,
|
|
host.DefaultClassName,
|
|
host.DefaultNamespace,
|
|
rootedFilePath,
|
|
shouldGenerateLinePragmas: true),
|
|
new ErrorSink());
|
|
var codeBuilder = new CSharpCodeBuilder(codeBuilderContext);
|
|
host.ChunkInheritanceUtility = chunkInheritanceUtility;
|
|
|
|
// Act
|
|
host.DecorateCodeBuilder(codeBuilder, codeBuilderContext);
|
|
|
|
// Assert
|
|
Assert.Equal("src/file.cshtml", chunkInheritanceUtility.InheritedCodeTreePagePath, StringComparer.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void MvcRazorHost_EnablesInstrumentationByDefault()
|
|
{
|
|
// Arrange
|
|
var fileProvider = new TestFileProvider();
|
|
var host = new MvcRazorHost(new DefaultCodeTreeCache(fileProvider));
|
|
|
|
// Act
|
|
var instrumented = host.EnableInstrumentation;
|
|
|
|
// Assert
|
|
Assert.True(instrumented);
|
|
}
|
|
|
|
[Fact]
|
|
public void MvcRazorHost_GeneratesTagHelperModelExpressionCode_DesignTime()
|
|
{
|
|
// Arrange
|
|
var fileProvider = new TestFileProvider();
|
|
var host = new MvcRazorHost(new DefaultCodeTreeCache(fileProvider))
|
|
{
|
|
DesignTimeMode = true
|
|
};
|
|
var expectedLineMappings = new List<LineMapping>
|
|
{
|
|
BuildLineMapping(documentAbsoluteIndex: 7,
|
|
documentLineIndex: 0,
|
|
documentCharacterIndex: 7,
|
|
generatedAbsoluteIndex: 444,
|
|
generatedLineIndex: 12,
|
|
generatedCharacterIndex: 7,
|
|
contentLength: 8),
|
|
BuildLineMapping(documentAbsoluteIndex: 33,
|
|
documentLineIndex: 2,
|
|
documentCharacterIndex: 14,
|
|
generatedAbsoluteIndex: 823,
|
|
generatedLineIndex: 25,
|
|
generatedCharacterIndex: 14,
|
|
contentLength: 85),
|
|
BuildLineMapping(documentAbsoluteIndex: 139,
|
|
documentLineIndex: 4,
|
|
documentCharacterIndex: 17,
|
|
generatedAbsoluteIndex: 2313,
|
|
generatedLineIndex: 55,
|
|
generatedCharacterIndex: 95,
|
|
contentLength: 3),
|
|
BuildLineMapping(
|
|
documentAbsoluteIndex: 166,
|
|
documentLineIndex: 5,
|
|
documentCharacterIndex: 18,
|
|
generatedAbsoluteIndex: 2626,
|
|
generatedLineIndex: 61,
|
|
generatedCharacterIndex: 87,
|
|
contentLength: 5),
|
|
};
|
|
|
|
// Act and Assert
|
|
RunDesignTimeTest(host,
|
|
testName: "ModelExpressionTagHelper",
|
|
expectedLineMappings: expectedLineMappings);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Basic")]
|
|
[InlineData("Inject")]
|
|
[InlineData("InjectWithModel")]
|
|
[InlineData("InjectWithSemicolon")]
|
|
[InlineData("Model")]
|
|
[InlineData("ModelExpressionTagHelper")]
|
|
public void MvcRazorHost_ParsesAndGeneratesCodeForBasicScenarios(string scenarioName)
|
|
{
|
|
// Arrange
|
|
var fileProvider = new TestFileProvider();
|
|
var host = new TestMvcRazorHost(new DefaultCodeTreeCache(fileProvider));
|
|
|
|
// Act and Assert
|
|
RunRuntimeTest(host, scenarioName);
|
|
}
|
|
|
|
[Fact]
|
|
public void InjectVisitor_GeneratesCorrectLineMappings()
|
|
{
|
|
// Arrange
|
|
var fileProvider = new TestFileProvider();
|
|
var host = new MvcRazorHost(new DefaultCodeTreeCache(fileProvider))
|
|
{
|
|
DesignTimeMode = true
|
|
};
|
|
host.NamespaceImports.Clear();
|
|
var expectedLineMappings = new List<LineMapping>
|
|
{
|
|
BuildLineMapping(1, 0, 1, 59, 3, 0, 17),
|
|
BuildLineMapping(28, 1, 8, 706, 26, 8, 20)
|
|
};
|
|
|
|
// Act and Assert
|
|
RunDesignTimeTest(host, "Inject", expectedLineMappings);
|
|
}
|
|
|
|
[Fact]
|
|
public void InjectVisitorWithModel_GeneratesCorrectLineMappings()
|
|
{
|
|
// Arrange
|
|
var fileProvider = new TestFileProvider();
|
|
var host = new MvcRazorHost(new DefaultCodeTreeCache(fileProvider))
|
|
{
|
|
DesignTimeMode = true
|
|
};
|
|
host.NamespaceImports.Clear();
|
|
var expectedLineMappings = new[]
|
|
{
|
|
BuildLineMapping(7, 0, 7, 214, 6, 7, 7),
|
|
BuildLineMapping(24, 1, 8, 731, 26, 8, 20),
|
|
BuildLineMapping(54, 2, 8, 957, 34, 8, 23)
|
|
};
|
|
|
|
// Act and Assert
|
|
RunDesignTimeTest(host, "InjectWithModel", expectedLineMappings);
|
|
}
|
|
|
|
[Fact]
|
|
public void InjectVisitorWithSemicolon_GeneratesCorrectLineMappings()
|
|
{
|
|
// Arrange
|
|
var fileProvider = new TestFileProvider();
|
|
var host = new MvcRazorHost(new DefaultCodeTreeCache(fileProvider))
|
|
{
|
|
DesignTimeMode = true
|
|
};
|
|
host.NamespaceImports.Clear();
|
|
var expectedLineMappings = new []
|
|
{
|
|
BuildLineMapping(7, 0, 7, 222, 6, 7, 7),
|
|
BuildLineMapping(24, 1, 8, 747, 26, 8, 20),
|
|
BuildLineMapping(58, 2, 8, 977, 34, 8, 23),
|
|
BuildLineMapping(93, 3, 8, 1210, 42, 8, 21),
|
|
BuildLineMapping(129, 4, 8, 1441, 50, 8, 24),
|
|
};
|
|
|
|
// Act and Assert
|
|
RunDesignTimeTest(host, "InjectWithSemicolon", expectedLineMappings);
|
|
}
|
|
|
|
[Fact]
|
|
public void ModelVisitor_GeneratesCorrectLineMappings()
|
|
{
|
|
// Arrange
|
|
var fileProvider = new TestFileProvider();
|
|
var host = new MvcRazorHost(new DefaultCodeTreeCache(fileProvider))
|
|
{
|
|
DesignTimeMode = true
|
|
};
|
|
host.NamespaceImports.Clear();
|
|
var expectedLineMappings = new[]
|
|
{
|
|
BuildLineMapping(7, 0, 7, 194, 6, 7, 30),
|
|
};
|
|
|
|
// Act and Assert
|
|
RunDesignTimeTest(host, "Model", expectedLineMappings);
|
|
}
|
|
|
|
private static void RunRuntimeTest(MvcRazorHost host,
|
|
string testName)
|
|
{
|
|
var inputFile = "TestFiles/Input/" + testName + ".cshtml";
|
|
var expectedCode = ReadResource("TestFiles/Output/Runtime/" + testName + ".cs");
|
|
|
|
// Act
|
|
GeneratorResults results;
|
|
using (var stream = GetResourceStream(inputFile))
|
|
{
|
|
results = host.GenerateCode(inputFile, stream);
|
|
}
|
|
|
|
// Assert
|
|
Assert.True(results.Success);
|
|
Assert.Equal(expectedCode, results.GeneratedCode);
|
|
Assert.Empty(results.ParserErrors);
|
|
}
|
|
|
|
private static void RunDesignTimeTest(MvcRazorHost host,
|
|
string testName,
|
|
IEnumerable<LineMapping> expectedLineMappings)
|
|
{
|
|
var inputFile = "TestFiles/Input/" + testName + ".cshtml";
|
|
var expectedCode = ReadResource("TestFiles/Output/DesignTime/" + testName + ".cs");
|
|
|
|
// Act
|
|
GeneratorResults results;
|
|
using (var stream = GetResourceStream(inputFile))
|
|
{
|
|
results = host.GenerateCode(inputFile, stream);
|
|
}
|
|
|
|
// Assert
|
|
Assert.True(results.Success);
|
|
Assert.Equal(expectedCode, results.GeneratedCode);
|
|
Assert.Empty(results.ParserErrors);
|
|
Assert.Equal(expectedLineMappings, results.DesignTimeLineMappings);
|
|
}
|
|
|
|
private static string ReadResource(string resourceName)
|
|
{
|
|
using (var stream = GetResourceStream(resourceName))
|
|
{
|
|
using (var streamReader = new StreamReader(stream))
|
|
{
|
|
return streamReader.ReadToEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Stream GetResourceStream(string resourceName)
|
|
{
|
|
resourceName = "Microsoft.AspNet.Mvc.Razor.Host.Test." + resourceName.Replace('/', '.');
|
|
|
|
var assembly = typeof(MvcRazorHostTest).Assembly;
|
|
return assembly.GetManifestResourceStream(resourceName);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
private class PathValidatingChunkInheritanceUtility : ChunkInheritanceUtility
|
|
{
|
|
public PathValidatingChunkInheritanceUtility(MvcRazorHost razorHost)
|
|
: base(razorHost, codeTreeCache: null, defaultInheritedChunks: new Chunk[0])
|
|
{
|
|
}
|
|
|
|
public string InheritedCodeTreePagePath { get; private set; }
|
|
|
|
public override IReadOnlyList<CodeTree> GetInheritedCodeTrees([NotNull] string pagePath)
|
|
{
|
|
InheritedCodeTreePagePath = pagePath;
|
|
|
|
return new CodeTree[0];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used when testing Tag Helpers, it disables the unique ID generation feature.
|
|
/// </summary>
|
|
private class TestMvcRazorHost : MvcRazorHost
|
|
{
|
|
public TestMvcRazorHost(ICodeTreeCache codeTreeCache)
|
|
: base(codeTreeCache)
|
|
{ }
|
|
|
|
public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeBuilderContext context)
|
|
{
|
|
base.DecorateCodeBuilder(incomingBuilder, context);
|
|
|
|
return new TestCSharpCodeBuilder(context,
|
|
DefaultModel,
|
|
"Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute",
|
|
new GeneratedTagHelperAttributeContext
|
|
{
|
|
ModelExpressionTypeName = ModelExpressionType,
|
|
CreateModelExpressionMethodName = CreateModelExpressionMethod
|
|
});
|
|
}
|
|
|
|
protected class TestCSharpCodeBuilder : MvcCSharpCodeBuilder
|
|
{
|
|
private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext;
|
|
|
|
public TestCSharpCodeBuilder(CodeBuilderContext context,
|
|
string defaultModel,
|
|
string activateAttribute,
|
|
GeneratedTagHelperAttributeContext tagHelperAttributeContext)
|
|
: base(context, defaultModel, activateAttribute, tagHelperAttributeContext)
|
|
{
|
|
_tagHelperAttributeContext = tagHelperAttributeContext;
|
|
}
|
|
|
|
protected override CSharpCodeVisitor CreateCSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
|
|
{
|
|
var visitor = base.CreateCSharpCodeVisitor(writer, context);
|
|
visitor.TagHelperRenderer = new NoUniqueIdsTagHelperCodeRenderer(visitor, writer, context)
|
|
{
|
|
AttributeValueCodeRenderer =
|
|
new MvcTagHelperAttributeValueCodeRenderer(_tagHelperAttributeContext)
|
|
};
|
|
return visitor;
|
|
}
|
|
|
|
private class NoUniqueIdsTagHelperCodeRenderer : CSharpTagHelperCodeRenderer
|
|
{
|
|
public NoUniqueIdsTagHelperCodeRenderer(IChunkVisitor bodyVisitor,
|
|
CSharpCodeWriter writer,
|
|
CodeBuilderContext context)
|
|
: base(bodyVisitor, writer, context)
|
|
{ }
|
|
|
|
protected override string GenerateUniqueId()
|
|
{
|
|
return "test";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |