context: // 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 System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  using Microsoft.AspNet.Http.Internal;
  using Microsoft.AspNet.Mvc.Rendering;
  using Microsoft.AspNet.PageExecutionInstrumentation;
  using Microsoft.AspNet.Razor.Runtime.TagHelpers;
  using Microsoft.AspNet.Testing;
  using Microsoft.Framework.WebEncoders.Testing;
  using Moq;
  using Xunit;
  
  namespace Microsoft.AspNet.Mvc.Razor
  {
      public class RazorPageTest
      {
  #pragma warning disable 1998
          private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = async writer => { };
  #pragma warning restore 1998
  
          [Fact]
          public async Task WritingScopesRedirectContentWrittenToViewContextWriter()
          {
              // Arrange
              var viewContext = CreateViewContext();
              var page = CreatePage(v =>
              {
                  v.HtmlEncoder = new CommonTestEncoder();
                  v.Write("Hello Prefix");
                  v.StartTagHelperWritingScope();
                  v.Write("Hello from Output");
                  v.ViewContext.Writer.Write("Hello from view context writer");
                  var scopeValue = v.EndTagHelperWritingScope();
                  v.Write("From Scope: " + scopeValue.ToString());
              });
  
              // Act
              await page.ExecuteAsync();
              var pageOutput = page.Output.ToString();
  
              // Assert
              Assert.Equal("HtmlEncode[[Hello Prefix]]HtmlEncode[[From Scope: HtmlEncode[[Hello from Output]]" +
                  "Hello from view context writer]]", pageOutput);
          }
  
          [Fact]
          public async Task WritingScopesRedirectsContentWrittenToOutput()
          {
              // Arrange
              var viewContext = CreateViewContext();
              var page = CreatePage(v =>
              {
                  v.HtmlEncoder = new CommonTestEncoder();
                  v.Write("Hello Prefix");
                  v.StartTagHelperWritingScope();
                  v.Write("Hello In Scope");
                  var scopeValue = v.EndTagHelperWritingScope();
                  v.Write("From Scope: " + scopeValue.ToString());
              });
  
              // Act
              await page.ExecuteAsync();
              var pageOutput = page.Output.ToString();
  
              // Assert
              Assert.Equal("HtmlEncode[[Hello Prefix]]HtmlEncode[[From Scope: HtmlEncode[[Hello In Scope]]]]", pageOutput);
          }
  
          [Fact]
          public async Task WritingScopesCanNest()
          {
              // Arrange
              var viewContext = CreateViewContext();
              var page = CreatePage(v =>
              {
                  v.HtmlEncoder = new CommonTestEncoder();
                  v.Write("Hello Prefix");
                  v.StartTagHelperWritingScope();
                  v.Write("Hello In Scope Pre Nest");
  
                  v.StartTagHelperWritingScope();
                  v.Write("Hello In Nested Scope");
                  var scopeValue1 = v.EndTagHelperWritingScope();
  
                  v.Write("Hello In Scope Post Nest");
                  var scopeValue2 = v.EndTagHelperWritingScope();
  
                  v.Write("From Scopes: " + scopeValue2.ToString() + scopeValue1.ToString());
              });
  
              // Act
              await page.ExecuteAsync();
              var pageOutput = page.Output.ToString();
  
              // Assert
              Assert.Equal("HtmlEncode[[Hello Prefix]]HtmlEncode[[From Scopes: HtmlEncode[[Hello In Scope Pre Nest]]" +
                  "HtmlEncode[[Hello In Scope Post Nest]]HtmlEncode[[Hello In Nested Scope]]]]", pageOutput);
          }
  
          [Fact]
          public async Task StartNewWritingScope_CannotFlushInWritingScope()
          {
              // Arrange
              var viewContext = CreateViewContext();
              var page = CreatePage(async v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
                  v.StartTagHelperWritingScope();
                  await v.FlushAsync();
              });
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(
                                  () => page.ExecuteAsync());
  
              // Assert
              Assert.Equal("The FlushAsync operation cannot be performed while " +
                  "inside a writing scope in '/Views/TestPath/Test.cshtml'.", ex.Message);
          }
  
          [Fact]
          public async Task StartNewWritingScope_CannotEndWritingScopeWhenNoWritingScope()
          {
              // Arrange
              var viewContext = CreateViewContext();
              var page = CreatePage(v =>
              {
                  v.EndTagHelperWritingScope();
              });
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(
                                  () => page.ExecuteAsync());
  
              // Assert
              Assert.Equal("There is no active writing scope to end.", ex.Message);
          }
  
          [Fact]
          public async Task EndTagHelperWritingScope_ReturnsAppropriateContent()
          {
              // Arrange
              var viewContext = CreateViewContext();
  
              // Act
              var page = CreatePage(v =>
              {
                  v.HtmlEncoder = new CommonTestEncoder();
                  v.StartTagHelperWritingScope();
                  v.Write("Hello World!");
                  var returnValue = v.EndTagHelperWritingScope();
  
                  // Assert
                  var content = Assert.IsType<DefaultTagHelperContent>(returnValue);
                  Assert.Equal("HtmlEncode[[Hello World!]]", content.GetContent());
              });
              await page.ExecuteAsync();
          }
  
          [Fact]
          public async Task EndTagHelperWritingScope_CopiesContent_IfRazorTextWriter()
          {
              // Arrange
              var viewContext = CreateViewContext();
  
              // Act
              var page = CreatePage(v =>
              {
                  v.HtmlEncoder = new CommonTestEncoder();
                  v.StartTagHelperWritingScope(new RazorTextWriter(TextWriter.Null, Encoding.UTF8));
                  v.Write("Hello ");
                  v.Write("World!");
                  var returnValue = v.EndTagHelperWritingScope();
  
                  // Assert
                  var content = Assert.IsType<DefaultTagHelperContent>(returnValue);
                  Assert.Equal("HtmlEncode[[Hello ]]HtmlEncode[[World!]]", content.GetContent());
                  Assert.Equal(new[] { "HtmlEncode[[Hello ]]", "HtmlEncode[[World!]]" }, content.ToArray());
              }, viewContext);
              await page.ExecuteAsync();
          }
  
          [Fact]
          public async Task DefineSection_ThrowsIfSectionIsAlreadyDefined()
          {
              // Arrange
              var viewContext = CreateViewContext();
              var page = CreatePage(v =>
              {
                  v.DefineSection("qux", _nullRenderAsyncDelegate);
                  v.DefineSection("qux", _nullRenderAsyncDelegate);
              });
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(
                                  () => page.ExecuteAsync());
  
              // Assert
              Assert.Equal("Section 'qux' is already defined.", ex.Message);
          }
  
          [Fact]
          public async Task RenderSection_RendersSectionFromPreviousPage()
          {
              // Arrange
              var expected = "Hello world";
              var viewContext = CreateViewContext();
              var page = CreatePage(v =>
              {
                  v.Write(v.RenderSection("bar"));
              });
              page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
              {
                  { "bar", writer => writer.WriteAsync(expected) }
              };
  
              // Act
              await page.ExecuteAsync();
  
              // Assert
              Assert.Equal(expected, page.RenderedContent);
          }
  
          [Fact]
          public async Task RenderSection_ThrowsIfPreviousSectionWritersIsNotSet()
          {
              // Arrange
              Exception ex = null;
              var page = CreatePage(v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
                  ex = Assert.Throws<InvalidOperationException>(() => v.RenderSection("bar"));
              });
  
              // Act
              await page.ExecuteAsync();
  
              // Assert
              Assert.Equal("RenderSection invocation in '/Views/TestPath/Test.cshtml' is invalid. " +
                  "RenderSection can only be called from a layout page.",
                  ex.Message);
          }
  
          [Fact]
          public async Task RenderSection_ThrowsIfRequiredSectionIsNotFound()
          {
              // Arrange
              var page = CreatePage(v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
                  v.RenderSection("bar");
              });
              page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
              {
                  { "baz", _nullRenderAsyncDelegate }
              };
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => page.ExecuteAsync());
  
              // Assert
              Assert.Equal("Section 'bar' is not defined in path '/Views/TestPath/Test.cshtml'.", ex.Message);
          }
  
          [Fact]
          public void IsSectionDefined_ThrowsIfPreviousSectionWritersIsNotRegistered()
          {
              // Arrange
              var page = CreatePage(v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
              });
  
              // Act and Assert
              page.ExecuteAsync();
              ExceptionAssert.Throws<InvalidOperationException>(() => page.IsSectionDefined("foo"),
                  "IsSectionDefined invocation in '/Views/TestPath/Test.cshtml' is invalid." +
                  " IsSectionDefined can only be called from a layout page.");
          }
  
          [Fact]
          public async Task IsSectionDefined_ReturnsFalseIfSectionNotDefined()
          {
              // Arrange
              bool? actual = null;
              var page = CreatePage(v =>
              {
                  actual = v.IsSectionDefined("foo");
                  v.RenderSection("baz");
                  v.RenderBodyPublic();
              });
              page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
              {
                  { "baz", _nullRenderAsyncDelegate }
              };
              page.RenderBodyDelegate = CreateBodyAction("body-content");
  
              // Act
              await page.ExecuteAsync();
  
              // Assert
              Assert.Equal(false, actual);
          }
  
          [Fact]
          public async Task IsSectionDefined_ReturnsTrueIfSectionDefined()
          {
              // Arrange
              bool? actual = null;
              var page = CreatePage(v =>
              {
                  actual = v.IsSectionDefined("baz");
                  v.RenderSection("baz");
                  v.RenderBodyPublic();
              });
              page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
              {
                  { "baz", _nullRenderAsyncDelegate }
              };
              page.RenderBodyDelegate = CreateBodyAction("body-content");
  
              // Act
              await page.ExecuteAsync();
  
              // Assert
              Assert.Equal(true, actual);
          }
  
          [Fact]
          public async Task RenderSection_ThrowsIfSectionIsRenderedMoreThanOnce()
          {
              // Arrange
              var expected = new HelperResult(action: null);
              var page = CreatePage(v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
                  v.RenderSection("header");
                  v.RenderSection("header");
              });
              page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
              {
                  { "header", _nullRenderAsyncDelegate }
              };
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
  
              // Assert
              Assert.Equal("RenderSectionAsync invocation in '/Views/TestPath/Test.cshtml' is invalid." +
                  " The section 'header' has already been rendered.", ex.Message);
          }
  
          [Fact]
          public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce()
          {
              // Arrange
              var expected = new HelperResult(action: null);
              var page = CreatePage(async v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
                  await v.RenderSectionAsync("header");
                  await v.RenderSectionAsync("header");
              });
              page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
              {
                  { "header", _nullRenderAsyncDelegate }
              };
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
  
              // Assert
              Assert.Equal("RenderSectionAsync invocation in '/Views/TestPath/Test.cshtml' is invalid." +
                  " The section 'header' has already been rendered.", ex.Message);
          }
  
          [Fact]
          public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce_WithSyncMethod()
          {
              // Arrange
              var expected = new HelperResult(action: null);
              var page = CreatePage(async v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
                  v.RenderSection("header");
                  await v.RenderSectionAsync("header");
              });
              page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
              {
                  { "header", _nullRenderAsyncDelegate }
              };
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
  
              // Assert
              Assert.Equal("RenderSectionAsync invocation in '/Views/TestPath/Test.cshtml' is invalid." +
                  " The section 'header' has already been rendered.", ex.Message);
          }
  
          [Fact]
          public async Task RenderSectionAsync_ThrowsIfNotInvokedFromLayoutPage()
          {
              // Arrange
              var expected = new HelperResult(action: null);
              var page = CreatePage(async v =>
              {
                  v.Path = "/Views/TestPath/Test.cshtml";
                  await v.RenderSectionAsync("header");
              });
  
              // Act
              var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
  
              // Assert
              Assert.Equal("RenderSectionAsync invocation in '/Views/TestPath/Test.cshtml' is invalid. " +
                  "RenderSectionAsync can only be called from a layout page.", ex.Message);
          }
  
          [Fact]
          public async Task EnsureRenderedBodyOrSections_ThrowsIfRenderBodyIsNotCalledFromPage_AndNoSectionsAreDefined()
          {
              // Arrange
  
gt:             var path = "page-path";