1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web.Razor; 6 using System.Web.Razor.Generator; 7 using System.Web.Razor.Parser; 8 using System.Web.Razor.Parser.SyntaxTree; 9 using System.Web.Razor.Test.Framework; 10 using System.Web.Razor.Text; 11 using Xunit; 12 13 namespace System.Web.Mvc.Razor.Test 14 { 15 public class MvcVBRazorCodeParserTest 16 { 17 [Fact] Constructor_AddsModelKeyword()18 public void Constructor_AddsModelKeyword() 19 { 20 var parser = new MvcVBRazorCodeParser(); 21 22 Assert.True(parser.IsDirectiveDefined(MvcVBRazorCodeParser.ModelTypeKeyword)); 23 } 24 25 [Fact] ParseModelKeyword_HandlesSingleInstance()26 public void ParseModelKeyword_HandlesSingleInstance() 27 { 28 // Arrange + Act 29 var document = "@ModelType Foo"; 30 var spans = ParseDocument(document); 31 32 // Assert 33 var factory = SpanFactory.CreateVbHtml(); 34 var expectedSpans = new Span[] 35 { 36 factory.EmptyHtml(), 37 factory.CodeTransition(SyntaxConstants.TransitionString) 38 .Accepts(AcceptedCharacters.None), 39 factory.MetaCode("ModelType ") 40 .Accepts(AcceptedCharacters.None), 41 factory.Code("Foo") 42 .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")) 43 }; 44 Assert.Equal(expectedSpans, spans.ToArray()); 45 } 46 47 [Fact] ParseModelKeyword_HandlesNullableTypes()48 public void ParseModelKeyword_HandlesNullableTypes() 49 { 50 // Arrange + Act 51 var document = "@ModelType Foo?\r\nBar"; 52 var spans = ParseDocument(document); 53 54 // Assert 55 var factory = SpanFactory.CreateVbHtml(); 56 var expectedSpans = new Span[] 57 { 58 factory.EmptyHtml(), 59 factory.CodeTransition(SyntaxConstants.TransitionString) 60 .Accepts(AcceptedCharacters.None), 61 factory.MetaCode("ModelType ") 62 .Accepts(AcceptedCharacters.None), 63 factory.Code("Foo?\r\n") 64 .As(new SetModelTypeCodeGenerator("Foo?", "{0}(Of {1})")), 65 factory.Markup("Bar") 66 }; 67 Assert.Equal(expectedSpans, spans.ToArray()); 68 } 69 70 [Fact] ParseModelKeyword_HandlesArrays()71 public void ParseModelKeyword_HandlesArrays() 72 { 73 // Arrange + Act 74 var document = "@ModelType Foo(())()\r\nBar"; 75 var spans = ParseDocument(document); 76 77 // Assert 78 var factory = SpanFactory.CreateVbHtml(); 79 var expectedSpans = new Span[] 80 { 81 factory.EmptyHtml(), 82 factory.CodeTransition(SyntaxConstants.TransitionString) 83 .Accepts(AcceptedCharacters.None), 84 factory.MetaCode("ModelType ") 85 .Accepts(AcceptedCharacters.None), 86 factory.Code("Foo(())()\r\n") 87 .As(new SetModelTypeCodeGenerator("Foo(())()", "{0}(Of {1})")), 88 factory.Markup("Bar") 89 }; 90 Assert.Equal(expectedSpans, spans.ToArray()); 91 } 92 93 [Fact] ParseModelKeyword_HandlesVSTemplateSyntax()94 public void ParseModelKeyword_HandlesVSTemplateSyntax() 95 { 96 // Arrange + Act 97 var document = "@ModelType $rootnamespace$.MyModel"; 98 var spans = ParseDocument(document); 99 100 // Assert 101 var factory = SpanFactory.CreateVbHtml(); 102 var expectedSpans = new Span[] 103 { 104 factory.EmptyHtml(), 105 factory.CodeTransition(SyntaxConstants.TransitionString) 106 .Accepts(AcceptedCharacters.None), 107 factory.MetaCode("ModelType ") 108 .Accepts(AcceptedCharacters.None), 109 factory.Code("$rootnamespace$.MyModel") 110 .As(new SetModelTypeCodeGenerator("$rootnamespace$.MyModel", "{0}(Of {1})")) 111 }; 112 Assert.Equal(expectedSpans, spans.ToArray()); 113 } 114 115 [Fact] ParseModelKeyword_ErrorOnMissingModelType()116 public void ParseModelKeyword_ErrorOnMissingModelType() 117 { 118 // Arrange + Act 119 List<RazorError> errors = new List<RazorError>(); 120 var document = "@ModelType "; 121 var spans = ParseDocument(document, errors); 122 123 // Assert 124 var factory = SpanFactory.CreateVbHtml(); 125 var expectedSpans = new Span[] 126 { 127 factory.EmptyHtml(), 128 factory.CodeTransition(SyntaxConstants.TransitionString) 129 .Accepts(AcceptedCharacters.None), 130 factory.MetaCode("ModelType ") 131 .Accepts(AcceptedCharacters.None), 132 factory.EmptyVB() 133 .As(new SetModelTypeCodeGenerator(String.Empty, "{0}(Of {1})")) 134 .Accepts(AcceptedCharacters.Any) 135 }; 136 var expectedErrors = new[] 137 { 138 new RazorError("The 'ModelType' keyword must be followed by a type name on the same line.", new SourceLocation(10, 0, 10), 1) 139 }; 140 Assert.Equal(expectedSpans, spans.ToArray()); 141 Assert.Equal(expectedErrors, errors.ToArray()); 142 } 143 144 [Fact] ParseModelKeyword_DoesNotAcceptNewlineIfInDesignTimeMode()145 public void ParseModelKeyword_DoesNotAcceptNewlineIfInDesignTimeMode() 146 { 147 // Arrange + Act 148 List<RazorError> errors = new List<RazorError>(); 149 var document = "@ModelType foo\r\n"; 150 var spans = ParseDocument(document, errors, designTimeMode: true); 151 152 // Assert 153 var factory = SpanFactory.CreateVbHtml(); 154 var expectedSpans = new Span[] 155 { 156 factory.EmptyHtml(), 157 factory.CodeTransition(SyntaxConstants.TransitionString) 158 .Accepts(AcceptedCharacters.None), 159 factory.MetaCode("ModelType ") 160 .Accepts(AcceptedCharacters.None), 161 factory.Code("foo") 162 .As(new SetModelTypeCodeGenerator("foo", "{0}(Of {1})")) 163 .Accepts(AcceptedCharacters.Any), 164 factory.Markup("\r\n") 165 }; 166 Assert.Equal(expectedSpans, spans.ToArray()); 167 Assert.Equal(0, errors.Count); 168 } 169 170 [Fact] ParseModelKeyword_ErrorOnMultipleModelStatements()171 public void ParseModelKeyword_ErrorOnMultipleModelStatements() 172 { 173 // Arrange + Act 174 List<RazorError> errors = new List<RazorError>(); 175 var document = 176 @"@ModelType Foo 177 @ModelType Bar"; 178 var spans = ParseDocument(document, errors); 179 180 // Assert 181 var factory = SpanFactory.CreateVbHtml(); 182 var expectedSpans = new Span[] 183 { 184 factory.EmptyHtml(), 185 factory.CodeTransition(SyntaxConstants.TransitionString) 186 .Accepts(AcceptedCharacters.None), 187 factory.MetaCode("ModelType ") 188 .Accepts(AcceptedCharacters.None), 189 factory.Code("Foo\r\n") 190 .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")), 191 factory.CodeTransition(SyntaxConstants.TransitionString) 192 .Accepts(AcceptedCharacters.None), 193 factory.MetaCode("ModelType ") 194 .Accepts(AcceptedCharacters.None), 195 factory.Code("Bar") 196 .As(new SetModelTypeCodeGenerator("Bar", "{0}(Of {1})")) 197 }; 198 199 var expectedErrors = new[] 200 { 201 new RazorError("Only one 'ModelType' statement is allowed in a file.", new SourceLocation(26, 1, 10), 1) 202 }; 203 expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span)); 204 Assert.Equal(expectedSpans, spans.ToArray()); 205 Assert.Equal(expectedErrors, errors.ToArray()); 206 } 207 208 [Fact] ParseModelKeyword_ErrorOnModelFollowedByInherits()209 public void ParseModelKeyword_ErrorOnModelFollowedByInherits() 210 { 211 // Arrange + Act 212 List<RazorError> errors = new List<RazorError>(); 213 var document = 214 @"@ModelType Foo 215 @Inherits Bar"; 216 var spans = ParseDocument(document, errors); 217 218 // Assert 219 var factory = SpanFactory.CreateVbHtml(); 220 var expectedSpans = new Span[] 221 { 222 factory.EmptyHtml(), 223 factory.CodeTransition(SyntaxConstants.TransitionString) 224 .Accepts(AcceptedCharacters.None), 225 factory.MetaCode("ModelType ") 226 .Accepts(AcceptedCharacters.None), 227 factory.Code("Foo\r\n") 228 .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")), 229 factory.CodeTransition(SyntaxConstants.TransitionString) 230 .Accepts(AcceptedCharacters.None), 231 factory.MetaCode("Inherits ") 232 .Accepts(AcceptedCharacters.None), 233 factory.Code("Bar") 234 .As(new SetBaseTypeCodeGenerator("Bar")) 235 }; 236 237 var expectedErrors = new[] 238 { 239 new RazorError("The 'inherits' keyword is not allowed when a 'ModelType' keyword is used.", new SourceLocation(25, 1, 9), 1) 240 }; 241 expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span)); 242 Assert.Equal(expectedSpans, spans.ToArray()); 243 Assert.Equal(expectedErrors, errors.ToArray()); 244 } 245 246 [Fact] ParseModelKeyword_ErrorOnInheritsFollowedByModel()247 public void ParseModelKeyword_ErrorOnInheritsFollowedByModel() 248 { 249 // Arrange + Act 250 List<RazorError> errors = new List<RazorError>(); 251 var document = 252 @"@Inherits Bar 253 @ModelType Foo"; 254 var spans = ParseDocument(document, errors); 255 256 // Assert 257 var factory = SpanFactory.CreateVbHtml(); 258 var expectedSpans = new Span[] 259 { 260 factory.EmptyHtml(), 261 factory.CodeTransition(SyntaxConstants.TransitionString) 262 .Accepts(AcceptedCharacters.None), 263 factory.MetaCode("Inherits ") 264 .Accepts(AcceptedCharacters.None), 265 factory.Code("Bar\r\n") 266 .AsBaseType("Bar"), 267 factory.CodeTransition(SyntaxConstants.TransitionString) 268 .Accepts(AcceptedCharacters.None), 269 factory.MetaCode("ModelType ") 270 .Accepts(AcceptedCharacters.None), 271 factory.Code("Foo") 272 .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")) 273 }; 274 275 var expectedErrors = new[] 276 { 277 new RazorError("The 'inherits' keyword is not allowed when a 'ModelType' keyword is used.", new SourceLocation(9, 0, 9), 1) 278 }; 279 expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span)); 280 Assert.Equal(expectedSpans, spans.ToArray()); 281 Assert.Equal(expectedErrors, errors.ToArray()); 282 } 283 ParseDocument(string documentContents, List<RazorError> errors = null, bool designTimeMode = false)284 private static List<Span> ParseDocument(string documentContents, List<RazorError> errors = null, bool designTimeMode = false) 285 { 286 errors = errors ?? new List<RazorError>(); 287 var markupParser = new HtmlMarkupParser(); 288 var codeParser = new MvcVBRazorCodeParser(); 289 var context = new ParserContext(new SeekableTextReader(documentContents), codeParser, markupParser, markupParser); 290 context.DesignTimeMode = designTimeMode; 291 codeParser.Context = context; 292 markupParser.Context = context; 293 markupParser.ParseDocument(); 294 295 ParserResults results = context.CompleteParse(); 296 foreach (RazorError error in results.ParserErrors) 297 { 298 errors.Add(error); 299 } 300 return results.Document.Flatten().ToList(); 301 } 302 } 303 } 304