// 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.Language; using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.VisualStudio.Text; using Xunit; namespace Microsoft.VisualStudio.Editor.Razor { public class DefaultRazorIndentationFactsServiceTest { [Fact] public void GetPreviousLineEndIndex_ReturnsPreviousLine() { // Arrange var textSnapshot = new StringTextSnapshot(@"@{

Hello World

}"); var line = textSnapshot.GetLineFromLineNumber(2); // Act var previousLineEndIndex = DefaultRazorIndentationFactsService.GetPreviousLineEndIndex(textSnapshot, line); // Assert Assert.Equal(24 + Environment.NewLine.Length, previousLineEndIndex); } [Fact] public void IsCSharpOpenCurlyBrace_SpanWithLeftBrace_ReturnTrue() { // Arrange var childBuilder = new SpanBuilder(SourceLocation.Zero); childBuilder.Accept(new CSharpToken("{", CSharpTokenType.LeftBrace)); var child = childBuilder.Build(); // Act var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child); // Assert Assert.True(result); } [Theory] [InlineData("if", CSharpTokenType.Keyword)] [InlineData("}", CSharpTokenType.RightBrace)] [InlineData("++", CSharpTokenType.Increment)] [InlineData("text", CSharpTokenType.Identifier)] public void IsCSharpOpenCurlyBrace_SpanWithUnsupportedSymbolType_ReturnFalse(string content, object symbolTypeObject) { // Arrange var symbolType = (CSharpTokenType)symbolTypeObject; var childBuilder = new SpanBuilder(SourceLocation.Zero); childBuilder.Accept(new CSharpToken(content, symbolType)); var child = childBuilder.Build(); // Act var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child); // Assert Assert.False(result); } [Fact] public void IsCSharpOpenCurlyBrace_MultipleSymbols_ReturnFalse() { // Arrange var childBuilder = new SpanBuilder(SourceLocation.Zero); childBuilder.Accept(new CSharpToken("hello", CSharpTokenType.Identifier)); childBuilder.Accept(new CSharpToken(",", CSharpTokenType.Comma)); var child = childBuilder.Build(); // Act var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child); // Assert Assert.False(result); } [Fact] public void IsCSharpOpenCurlyBrace_SpanWithHtmlSymbol_ReturnFalse() { // Arrange var childBuilder = new SpanBuilder(SourceLocation.Zero); childBuilder.Accept(new HtmlToken("hello", HtmlTokenType.Text)); var child = childBuilder.Build(); // Act var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child); // Assert Assert.False(result); } [Fact] public void IsCSharpOpenCurlyBrace_Blocks_ReturnFalse() { // Arrange var child = new BlockBuilder() { Type = BlockKindInternal.Markup, }.Build(); // Act var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child); // Assert Assert.False(result); } [Fact] public void GetIndentLevelOfLine_AddsTabsOnlyAtBeginningOfLine() { // Arrange var text = "\t\tHello\tWorld.\t"; var service = new DefaultRazorIndentationFactsService(); // Act var indentLevel = service.GetIndentLevelOfLine(text, 4); // Assert Assert.Equal(8, indentLevel); } [Fact] public void GetIndentLevelOfLine_AddsSpacesOnlyAtBeginningOfLine() { // Arrange var text = " Hello World. "; var service = new DefaultRazorIndentationFactsService(); // Act var indentLevel = service.GetIndentLevelOfLine(text, 4); // Assert Assert.Equal(3, indentLevel); } [Fact] public void GetIndentLevelOfLine_AddsTabsAndSpacesOnlyAtBeginningOfLine() { // Arrange var text = " \t \tHello\t World.\t "; var service = new DefaultRazorIndentationFactsService(); // Act var indentLevel = service.GetIndentLevelOfLine(text, 4); // Assert Assert.Equal(11, indentLevel); } [Fact] public void GetIndentLevelOfLine_NoIndent() { // Arrange var text = "Hello World."; var service = new DefaultRazorIndentationFactsService(); // Act var indentLevel = service.GetIndentLevelOfLine(text, 4); // Assert Assert.Equal(0, indentLevel); } // This test verifies that we still operate on SyntaxTree's that have gaps in them. The gaps are temporary // until our work with the parser has been completed. [Fact] public void GetDesiredIndentation_ReturnsNull_IfOwningSpanDoesNotExist() { // Arrange var source = new StringTextSnapshot($@"
"); var syntaxTree = GetSyntaxTree(new StringTextSnapshot("something else")); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, source, source.GetLineFromLineNumber(3), indentSize: 4, tabSize: 1); // Assert Assert.Null(indentation); } [Fact] public void GetDesiredIndentation_ReturnsNull_IfOwningSpanIsCode() { // Arrange var source = new StringTextSnapshot($@" @{{ "); var syntaxTree = GetSyntaxTree(source); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, source, source.GetLineFromLineNumber(2), indentSize: 4, tabSize: 1); // Assert Assert.Null(indentation); } [Fact] public void GetDesiredIndentation_ReturnsNull_IfOwningSpanIsNone() { // Arrange var customDirective = DirectiveDescriptor.CreateSingleLineDirective("custom"); var source = new StringTextSnapshot($@" @custom "); var syntaxTree = GetSyntaxTree(source, new[] { customDirective }); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, source, source.GetLineFromLineNumber(2), indentSize: 4, tabSize: 1); // Assert Assert.Null(indentation); } [Fact] public void GetDesiredIndentation_ReturnsCorrectIndentation_ForMarkupWithinCodeBlock() { // Arrange var source = new StringTextSnapshot($@"@{{
"); var syntaxTree = GetSyntaxTree(source); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, source, source.GetLineFromLineNumber(2), indentSize: 4, tabSize: 4); // Assert Assert.Equal(4, indentation); } [Fact] public void GetDesiredIndentation_ReturnsCorrectIndentation_ForMarkupWithinDirectiveBlock() { // Arrange var customDirective = DirectiveDescriptor.CreateRazorBlockDirective("custom"); var source = new StringTextSnapshot($@"@custom {{
}}"); var syntaxTree = GetSyntaxTree(source, new[] { customDirective }); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, source, source.GetLineFromLineNumber(3), indentSize: 4, tabSize: 4); // Assert Assert.Equal(4, indentation); } [Fact] public void GetDesiredIndentation_ReturnsCorrectIndentation_ForNestedMarkupWithinCodeBlock() { // Arrange var source = new StringTextSnapshot($@"
@{{ }}
"); var syntaxTree = GetSyntaxTree(source); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, source, source.GetLineFromLineNumber(4), indentSize: 4, tabSize: 4); // Assert Assert.Equal(8, indentation); } [Fact] public void GetDesiredIndentation_ReturnsCorrectIndentation_ForMarkupWithinCodeBlockInADirectiveBlock() { // Arrange var customDirective = DirectiveDescriptor.CreateRazorBlockDirective("custom"); var source = new StringTextSnapshot($@"@custom {{ @{{
}} }}"); var syntaxTree = GetSyntaxTree(source, new[] { customDirective }); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, source, source.GetLineFromLineNumber(4), indentSize: 4, tabSize: 4); // Assert Assert.Equal(8, indentation); } private static RazorSyntaxTree GetSyntaxTree(StringTextSnapshot source, IEnumerable directives = null) { directives = directives ?? Enumerable.Empty(); var engine = RazorProjectEngine.Create(builder => { foreach (var directive in directives) { builder.AddDirective(directive); } }); var sourceProjectItem = new TestRazorProjectItem("test.cshtml") { Content = source.GetText() }; var codeDocument = engine.ProcessDesignTime(sourceProjectItem); return codeDocument.GetSyntaxTree(); } } }