// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Globalization; using System.Web.WebPages.Resources; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.WebPages.Test { public class LayoutTest { [Fact] public void LayoutBasicTest() { var layoutPath = "~/Layout.cshtml"; LayoutBasicTestInternal(layoutPath); } [Fact] public void RelativeLayoutPageTest() { var pagePath = "~/MyApp/index.cshtml"; var layoutPath = "~/MyFiles/Layout.cshtml"; var layoutPage = "../MyFiles/Layout.cshtml"; LayoutBasicTestInternal(layoutPath, pagePath, layoutPage); } [Fact] public void AppRelativeLayoutPageTest() { var pagePath = "~/MyApp/index.cshtml"; var layoutPath = "~/MyFiles/Layout.cshtml"; var layoutPage = "~/MyFiles/Layout.cshtml"; LayoutBasicTestInternal(layoutPath, pagePath, layoutPage); } [Fact] public void SourceFileWithLayoutPageTest() { // Arrange var pagePath = "~/MyApp/index.cshtml"; var layoutPath = "~/MyFiles/Layout.cshtml"; var layoutPage = "~/MyFiles/Layout.cshtml"; var content = "hello world"; var title = "MyPage"; var page = CreatePageWithLayout( p => { p.PageData["Title"] = title; p.WriteLiteral(content); }, p => { p.WriteLiteral(p.PageData["Title"]); p.Write(p.RenderBody()); }, pagePath, layoutPath, layoutPage); var request = new Mock(); request.SetupGet(c => c.Path).Returns("/myapp/index.cshtml"); request.SetupGet(c => c.RawUrl).Returns("http://localhost:8080/index.cshtml"); request.SetupGet(c => c.IsLocal).Returns(true); request.Setup(c => c.MapPath(It.IsAny())).Returns(c => c); request.Setup(c => c.Browser.IsMobileDevice).Returns(false); request.Setup(c => c.Cookies).Returns(new HttpCookieCollection()); var result = Utils.RenderWebPage(page, request: request.Object); Assert.Equal(2, page.PageContext.SourceFiles.Count); Assert.True(page.PageContext.SourceFiles.Contains("~/MyApp/index.cshtml")); Assert.True(page.PageContext.SourceFiles.Contains("~/MyFiles/Layout.cshtml")); } private static void LayoutBasicTestInternal(string layoutPath, string pagePath = "~/index.cshtml", string layoutPage = "Layout.cshtml") { // The page ~/index.cshtml does the following: // PageData["Title"] = "MyPage"; // Layout = "Layout.cshtml"; // WriteLiteral("hello world"); // // The layout page ~/Layout.cshtml does the following: // WriteLiteral(Title); // RenderBody(); // // Expected rendered result is "MyPagehello world" var content = "hello world"; var title = "MyPage"; var result = RenderPageWithLayout( p => { p.PageData["Title"] = title; p.WriteLiteral(content); }, p => { p.WriteLiteral(p.PageData["Title"]); p.Write(p.RenderBody()); }, pagePath, layoutPath, layoutPage); Assert.Equal(title + content, result); } [Fact] public void LayoutNestedTest() { // Testing nested layout pages // // The page ~/index.cshtml does the following: // PageData["Title"] = "MyPage"; // Layout = "Layout1.cshtml"; // WriteLiteral("hello world"); // // The first layout page ~/Layout1.cshtml does the following: // Layout = "Layout2.cshtml"; // WriteLiteral(""); // RenderBody(); // WriteLiteral(""); // // The second layout page ~/Layout2.cshtml does the following: // WriteLiteral(Title); // WriteLiteral(""); // RenderBody(); // WriteLiteral(""); // // Expected rendered result is "MyPagehello world" var layout2Path = "~/Layout2.cshtml"; var layout2 = Utils.CreatePage( p => { p.WriteLiteral(p.PageData["Title"]); p.WriteLiteral(""); p.Write(p.RenderBody()); p.WriteLiteral(""); }, layout2Path); var layout1Path = "~/Layout1.cshtml"; var layout1 = Utils.CreatePage( p => { p.Layout = "Layout2.cshtml"; p.WriteLiteral(""); p.Write(p.RenderBody()); p.WriteLiteral(""); }, layout1Path); var page = Utils.CreatePage( p => { p.PageData["Title"] = "MyPage"; p.Layout = "Layout1.cshtml"; p.WriteLiteral("hello world"); }); Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layout1, layout2); var result = Utils.RenderWebPage(page); Assert.Equal("MyPagehello world", result); } [Fact] public void LayoutSectionsTest() { // Testing nested layout pages with sections // // The page ~/index.cshtml does the following: // PageData["Title"] = "MyPage"; // Layout = "Layout1.cshtml"; // DefineSection("header1", () => { // WriteLiteral("index header"); // }); // WriteLiteral("hello world"); // DefineSection("footer1", () => { // WriteLiteral("index footer"); // }); // // The first layout page ~/Layout1.cshtml does the following: // Layout = "Layout2.cshtml"; // DefineSection("header2", () => { // WriteLiteral(""); // RenderSection("header1"); // WriteLiteral(""); // }); // WriteLiteral(""); // RenderBody(); // WriteLiteral(""); // DefineSection("footer2", () => { // WriteLiteral(""); // RenderSection("header2"); // WriteLiteral(""); // }); // // The second layout page ~/Layout2.cshtml does the following: // WriteLiteral(Title); // WriteLiteral("\n"); // RenderSection("header2"); // WriteLiteral("\n"); // WriteLiteral(""); // RenderBody(); // WriteLiteral("\n"); // WriteLiteral(""); // RenderSection("footer"); // WriteLiteral(""); // // Expected rendered result is: // MyPage // index header // hello world" // index footer var layout2Path = "~/Layout2.cshtml"; var layout2 = Utils.CreatePage( p => { p.WriteLiteral(p.PageData["Title"]); p.WriteLiteral("\r\n"); p.WriteLiteral(""); p.Write(p.RenderSection("header2")); p.WriteLiteral(""); p.WriteLiteral("\r\n"); p.WriteLiteral(""); p.Write(p.RenderBody()); p.WriteLiteral(""); p.WriteLiteral("\r\n"); p.WriteLiteral(""); p.Write(p.RenderSection("footer2")); p.WriteLiteral(""); }, layout2Path); var layout1Path = "~/Layout1.cshtml"; var layout1 = Utils.CreatePage( p => { p.Layout = "Layout2.cshtml"; p.DefineSection("header2", () => { p.WriteLiteral(""); p.Write(p.RenderSection("header1")); p.WriteLiteral(""); }); p.WriteLiteral(""); p.Write(p.RenderBody()); p.WriteLiteral(""); p.DefineSection("footer2", () => { p.WriteLiteral(""); p.Write(p.RenderSection("footer1")); p.WriteLiteral(""); }); }, layout1Path); var page = Utils.CreatePage( p => { p.PageData["Title"] = "MyPage"; p.Layout = "Layout1.cshtml"; p.DefineSection("header1", () => { p.WriteLiteral("index header"); }); p.WriteLiteral("hello world"); p.DefineSection("footer1", () => { p.WriteLiteral("index footer"); }); }); Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layout1, layout2); var result = Utils.RenderWebPage(page); var expected = @"MyPage index header hello world index footer"; Assert.Equal(expected, result); } [Fact] public void LayoutSectionsNestedNamesTest() { // Tests nested layout using the same section names at different levels. // // The page ~/index.cshtml does the following: // Layout = "Layout1.cshtml"; // @section body { // body in index // } // // The page ~/layout1.cshtml does the following: // Layout = "Layout2.cshtml"; // @section body { // body in layout1 // @RenderSection("body") // } // // The page ~/layout2.cshtml does the following: // body in layout2 // @RenderSection("body") // // Expected rendered result is: // body in layout2 body in layout1 body in index var layout2Path = "~/Layout2.cshtml"; var layout2 = Utils.CreatePage( p => { p.WriteLiteral("body in layout2 "); p.Write(p.RenderSection("body")); }, layout2Path); var layout1Path = "~/Layout1.cshtml"; var layout1 = Utils.CreatePage( p => { p.Layout = "Layout2.cshtml"; p.DefineSection("body", () => { p.WriteLiteral("body in layout1 "); p.Write(p.RenderSection("body")); }); }, layout1Path); var page = Utils.CreatePage( p => { p.Layout = "Layout1.cshtml"; p.DefineSection("body", () => { p.WriteLiteral("body in index"); }); }); Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layout1, layout2); var result = Utils.RenderWebPage(page); var expected = "body in layout2 body in layout1 body in index"; Assert.Equal(expected, result); } [Fact] public void CaseInsensitiveSectionNamesTest() { var page = CreatePageWithLayout( p => { p.Write("123"); p.DefineSection("abc", () => { p.Write("abc"); }); p.DefineSection("XYZ", () => { p.Write("xyz"); }); p.Write("456"); }, p => { p.Write(p.RenderSection("AbC")); p.Write(p.RenderSection("xyZ")); p.Write(p.RenderBody()); }); var result = Utils.RenderWebPage(page); var expected = "abcxyz123456"; Assert.Equal(expected, result); } [Fact] public void MissingLayoutPageTest() { var layoutPage = "Layout.cshtml"; var page = Utils.CreatePage( p => { p.PageData["Title"] = "MyPage"; p.Layout = layoutPage; }); var layoutPath1 = "~/Layout.cshtml"; Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_LayoutPageNotFound, layoutPage, layoutPath1)); } [Fact] public void RenderBodyAlreadyCalledTest() { // Layout page calls RenderBody more than once. var page = CreatePageWithLayout( p => { }, p => { p.Write(p.RenderBody()); p.Write(p.RenderBody()); }); Assert.Throws(() => Utils.RenderWebPage(page), WebPageResources.WebPage_RenderBodyAlreadyCalled); } [Fact] public void RenderBodyNotCalledTest() { // Page does not define any sections, but layout page does not call RenderBody var layoutPath = "~/Layout.cshtml"; var page = CreatePageWithLayout( p => { }, p => { }, layoutPath: layoutPath); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_RenderBodyNotCalled, layoutPath)); } [Fact] public void RenderBodyCalledDirectlyTest() { // A Page that is not a layout page calls the RenderBody method var page = Utils.CreatePage(p => { p.RenderBody(); }); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_CannotRequestDirectly, "~/index.cshtml", "RenderBody")); } [Fact] public void RenderSectionCalledDirectlyTest() { // A Page that is not a layout page calls the RenderBody method var page = Utils.CreatePage(p => { p.RenderSection(""); }); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_CannotRequestDirectly, "~/index.cshtml", "RenderSection")); } [Fact] public void SectionAlreadyDefinedTest() { // The page calls DefineSection more than once on the same name var sectionName = "header"; var page = Utils.CreatePage(p => { p.Layout = "Layout.cshtml"; p.DefineSection(sectionName, () => { }); p.DefineSection(sectionName, () => { }); }); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyDefined, sectionName)); } [Fact] public void SectionAlreadyDefinedCaseInsensitiveTest() { // The page calls DefineSection more than once on the same name but with different casing var name1 = "section1"; var name2 = "SecTion1"; var page = Utils.CreatePage(p => { p.Layout = "Layout.cshtml"; p.DefineSection(name1, () => { }); p.DefineSection(name2, () => { }); }); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyDefined, name2)); } [Fact] public void SectionNotDefinedTest() { // Layout page calls RenderSection on a name that has not been defined. var sectionName = "NoSuchSection"; var page = CreatePageWithLayout( p => { }, p => { p.Write(p.RenderSection(sectionName)); }); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionNotDefined, sectionName)); } [Fact] public void SectionAlreadyRenderedTest() { // Layout page calls RenderSection on the same name more than once. var sectionName = "header"; var page = CreatePageWithLayout( p => { p.Layout = "Layout.cshtml"; p.DefineSection(sectionName, () => { }); }, p => { p.Write(p.RenderSection(sectionName)); p.Write(p.RenderSection(sectionName)); }); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.InvariantCulture, WebPageResources.WebPage_SectionAleadyRendered, sectionName)); } [Fact] public void SectionsNotRenderedTest() { // Layout page does not render all the defined sections. var layoutPath = "~/Layout.cshtml"; var sectionName1 = "section1"; var sectionName2 = "section2"; var sectionName3 = "section3"; var sectionName4 = "section4"; var sectionName5 = "section5"; // A dummy section action that does nothing SectionWriter sectionAction = () => { }; // The page defines 5 sections. var page = CreatePageWithLayout( p => { p.DefineSection(sectionName1, sectionAction); p.DefineSection(sectionName2, sectionAction); p.DefineSection(sectionName3, sectionAction); p.DefineSection(sectionName4, sectionAction); p.DefineSection(sectionName5, sectionAction); }, // The layout page renders only two of the sections p => { p.Write(p.RenderSection(sectionName2)); p.Write(p.RenderSection(sectionName4)); }, layoutPath: layoutPath); var sectionsNotRendered = "section1; section3; section5"; Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_SectionsNotRendered, layoutPath, sectionsNotRendered)); } [Fact] public void SectionsNotRenderedRenderBodyTest() { // Layout page does not render all the defined sections, but it calls RenderBody. var layoutPath = "~/Layout.cshtml"; var sectionName1 = "section1"; var sectionName2 = "section2"; // A dummy section action that does nothing SectionWriter sectionAction = () => { }; var page = CreatePageWithLayout( p => { p.DefineSection(sectionName1, sectionAction); p.DefineSection(sectionName2, sectionAction); }, // The layout page only calls RenderBody p => { p.Write(p.RenderBody()); }, layoutPath: layoutPath); var sectionsNotRendered = "section1; section2"; Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_SectionsNotRendered, layoutPath, sectionsNotRendered)); } [Fact] public void InvalidPageTypeTest() { var layoutPath = "~/Layout.js"; var contents = "hello world"; var page = Utils.CreatePage(p => { p.Layout = layoutPath; p.Write(contents); }); var layoutPage = new object(); var objectFactory = new Mock(); objectFactory.Setup(c => c.Exists(It.IsAny())).Returns(p => layoutPath.Equals(p, StringComparison.OrdinalIgnoreCase)); objectFactory.Setup(c => c.CreateInstance(It.IsAny())).Returns(_ => layoutPage as WebPageBase); page.VirtualPathFactory = objectFactory.Object; Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_InvalidPageType, layoutPath)); Assert.Throws(() => Utils.RenderWebPage(page), String.Format(CultureInfo.CurrentCulture, WebPageResources.WebPage_InvalidPageType, layoutPath)); } [Fact] public void ValidPageTypeTest() { var layoutPath = "~/Layout.js"; var contents = "hello world"; var page = Utils.CreatePage(p => { p.Layout = layoutPath; p.Write(contents); }); var layoutPage = Utils.CreatePage(p => p.WriteLiteral(p.RenderBody()), layoutPath); Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layoutPage); Assert.Equal(contents, Utils.RenderWebPage(page)); } [Fact] public void IsSectionDefinedTest() { // Tests for the IsSectionDefined method // Only sections named section1 and section3 are defined. var page = CreatePageWithLayout( p => { p.DefineSection("section1", () => { }); p.DefineSection("section3", () => { }); }, p => { p.Write(p.RenderSection("section1")); p.Write(p.RenderSection("section3")); p.Write("section1: " + p.IsSectionDefined("section1") + "; "); p.Write("section2: " + p.IsSectionDefined("section2") + "; "); p.Write("section3: " + p.IsSectionDefined("section3") + "; "); p.Write("section4: " + p.IsSectionDefined("section4") + "; "); }); var result = Utils.RenderWebPage(page); var expected = "section1: True; section2: False; section3: True; section4: False; "; Assert.Equal(expected, result); } [Fact] public void OptionalSectionsTest() { // Only sections named section1 and section3 are defined. var page = CreatePageWithLayout( p => { p.DefineSection("section1", () => { p.Write("section1 "); }); p.DefineSection("section3", () => { p.Write("section3"); }); }, p => { p.Write(p.RenderSection("section1", required: false)); p.Write(p.RenderSection("section2", required: false)); p.Write(p.RenderSection("section3", required: false)); p.Write(p.RenderSection("section4", required: false)); }); var result = Utils.RenderWebPage(page); var expected = "section1 section3"; Assert.Equal(expected, result); } [Fact] public void PageDataTest() { // Layout page uses items in PageData set by content page var contents = "my contents"; var page = CreatePageWithLayout( p => { p.PageData["contents"] = contents; p.Write(" body"); }, p => { p.Write(p.PageData["contents"]); p.Write(p.RenderBody()); }); var result = Utils.RenderWebPage(page); var expected = contents + " body"; Assert.Equal(expected, result); } [Fact] public void RenderPageAndLayoutPage() { //Dev10 bug 928341 - a page that has a layout page, and the page calls RenderPage should not cause an error var layoutPagePath = "~/layout.cshtml"; var page = Utils.CreatePage(p => { p.DefineSection("foo", () => { p.Write("This is foo"); }); p.Write(p.RenderPage("bar.cshtml")); p.Layout = layoutPagePath; }); var layoutPage = Utils.CreatePage(p => { p.Write(p.RenderBody()); p.Write(" "); p.Write(p.RenderSection("foo")); }, layoutPagePath); var subPage = Utils.CreatePage(p => p.Write("This is bar"), "~/bar.cshtml"); Utils.AssignObjectFactoriesAndDisplayModeProvider(page, layoutPage, subPage); var result = Utils.RenderWebPage(page); var expected = "This is bar This is foo"; Assert.Equal(expected, result); } public static string RenderPageWithLayout(Action pageExecuteAction, Action layoutExecuteAction, string pagePath = "~/index.cshtml", string layoutPath = "~/Layout.cshtml", string layoutPage = "Layout.cshtml") { var page = CreatePageWithLayout(pageExecuteAction, layoutExecuteAction, pagePath, layoutPath, layoutPage); return Utils.RenderWebPage(page); } public static MockPage CreatePageWithLayout(Action pageExecuteAction, Action layoutExecuteAction, string pagePath = "~/index.cshtml", string layoutPath = "~/Layout.cshtml", string layoutPageName = "Layout.cshtml") { var page = Utils.CreatePage( p => { p.Layout = layoutPageName; pageExecuteAction(p); }, pagePath); var layoutPage = Utils.CreatePage( p => { layoutExecuteAction(p); }, layoutPath); Utils.AssignObjectFactoriesAndDisplayModeProvider(layoutPage, page); return page; } } }