// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Web.Hosting; using System.Web.WebPages.Scope; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace Microsoft.Web.Helpers.Test { public class ThemesTest { [Fact] public void Initialize_WithBadParams_Throws() { // Arrange var mockVpp = new Mock().Object; var scope = new ScopeStorageDictionary(); // Act and Assert Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize(null, "foo"), "themeDirectory"); Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize("", "foo"), "themeDirectory"); Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize("~/folder", null), "defaultTheme"); Assert.ThrowsArgumentNullOrEmptyString(() => new ThemesImplementation(mockVpp, scope).Initialize("~/folder", ""), "defaultTheme"); } [Fact] public void CurrentThemeThrowsIfAssignedNullOrEmpty() { // Arrange var mockVpp = new Mock().Object; var scope = new ScopeStorageDictionary(); var themesImpl = new ThemesImplementation(mockVpp, scope); // Act and Assert Assert.ThrowsArgumentNullOrEmptyString(() => { themesImpl.CurrentTheme = null; }, "value"); Assert.ThrowsArgumentNullOrEmptyString(() => { themesImpl.CurrentTheme = String.Empty; }, "value"); } [Fact] public void InvokingPropertiesAndMethodsBeforeInitializationThrows() { // Arrange var mockVpp = new Mock().Object; var scope = new ScopeStorageDictionary(); var themesImpl = new ThemesImplementation(mockVpp, scope); // Act and Assert Assert.Throws(() => themesImpl.CurrentTheme = "Foo", @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class."); Assert.Throws(() => { var x = themesImpl.CurrentTheme; }, @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class."); Assert.Throws(() => { var x = themesImpl.AvailableThemes; }, @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class."); Assert.Throws(() => { var x = themesImpl.DefaultTheme; }, @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class."); Assert.Throws(() => { var x = themesImpl.GetResourcePath("baz"); }, @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class."); Assert.Throws(() => { var x = themesImpl.GetResourcePath("baz", "some-file"); }, @"You must call the ""Themes.Initialize"" method before you call any other method of the ""Themes"" class."); } [Fact] public void InitializeThrowsIfDefaultThemeDirectoryDoesNotExist() { // Arrange var defaultTheme = "default-theme"; var themeDirectory = "theme-directory"; var scope = new ScopeStorageDictionary(); var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir("not-default-theme")), scope); // Act And Assert Assert.ThrowsArgument( () => themesImpl.Initialize(themeDirectory: themeDirectory, defaultTheme: defaultTheme), "defaultTheme", "Unknown theme 'default-theme'. Ensure that a directory labeled 'default-theme' exists under the theme directory."); } [Fact] public void ThemesImplUsesScopeStorageToStoreProperties() { // Arrange var defaultTheme = "default-theme"; var themeDirectory = "theme-directory"; var scope = new ScopeStorageDictionary(); var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme)), scope); // Act themesImpl.Initialize(themeDirectory: themeDirectory, defaultTheme: defaultTheme); // Ensure Theme use scope storage to store properties Assert.Equal(scope[ThemesImplementation.ThemesInitializedKey], true); Assert.Equal(scope[ThemesImplementation.ThemeDirectoryKey], themeDirectory); Assert.Equal(scope[ThemesImplementation.DefaultThemeKey], defaultTheme); } [Fact] public void ThemesImplUsesDefaultThemeWhenNoCurrentThemeIsSpecified() { // Arrange var defaultTheme = "default-theme"; var themeDirectory = "theme-directory"; var scope = new ScopeStorageDictionary(); var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme)), scope); themesImpl.Initialize(themeDirectory, defaultTheme); // Act and Assert // CurrentTheme falls back to default theme when null Assert.Equal(themesImpl.CurrentTheme, defaultTheme); } [Fact] public void ThemesImplThrowsIfCurrentThemeIsInvalid() { // Arrange var defaultTheme = "default-theme"; var themeDirectory = "theme-directory"; var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme), new Dir("not-a-random-value")), new ScopeStorageDictionary()); themesImpl.Initialize(themeDirectory, defaultTheme); // Act and Assert Assert.ThrowsArgument(() => themesImpl.CurrentTheme = "random-value", "value", "Unknown theme 'random-value'. Ensure that a directory labeled 'random-value' exists under the theme directory."); } [Fact] public void ThemesImplUsesScopeStorageToStoreCurrentTheme() { // Arrange var defaultTheme = "default-theme"; var themeDirectory = "theme-directory"; var currentThemeDir = "custom-theme-dir"; var scope = new ScopeStorageDictionary(); var themesImpl = new ThemesImplementation(GetVirtualPathProvider(themeDirectory, new Dir(defaultTheme), new Dir("custom-theme-dir")), scope); // Act themesImpl.Initialize(themeDirectory, defaultTheme); themesImpl.CurrentTheme = currentThemeDir; // Assert Assert.Equal(scope[ThemesImplementation.CurrentThemeKey], currentThemeDir); } [Fact] public void GetResourcePathThrowsIfCurrentDirectoryIsNull() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile", "wp7.css"))); themesImpl.Initialize("themes", "default"); // Act and Assert Assert.ThrowsArgumentNull(() => themesImpl.GetResourcePath(folder: null, fileName: "wp7.css"), "folder"); } [Fact] public void GetResourcePathThrowsIfFileNameIsNullOrEmpty() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile", "wp7.css"))); themesImpl.Initialize("themes", "default"); // Act and Assert Assert.ThrowsArgumentNullOrEmptyString(() => themesImpl.GetResourcePath(folder: String.Empty, fileName: null), "fileName"); Assert.ThrowsArgumentNullOrEmptyString(() => themesImpl.GetResourcePath(folder: String.Empty, fileName: String.Empty), "fileName"); } [Fact] public void GetResourcePathReturnsItemFromThemeRootIfAvailable() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile", "wp7.css"))); themesImpl.Initialize("themes", "default"); // Act themesImpl.CurrentTheme = "mobile"; var themePath = themesImpl.GetResourcePath(fileName: "wp7.css"); // Assert Assert.Equal(themePath, @"themes/mobile/wp7.css"); } [Fact] public void GetResourcePathReturnsItemFromCurrentThemeDirectoryIfAvailable() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css"))); themesImpl.Initialize("themes", "default"); // Act themesImpl.CurrentTheme = "mobile"; var themePath = themesImpl.GetResourcePath(folder: "styles", fileName: "wp7.css"); // Assert Assert.Equal(themePath, @"themes/mobile/styles/wp7.css"); } [Fact] public void GetResourcePathReturnsItemFromDefaultThemeDirectoryIfNotFoundInCurrentThemeDirectory() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css"))); themesImpl.Initialize("themes", "default"); // Act themesImpl.CurrentTheme = "mobile"; var themePath = themesImpl.GetResourcePath(folder: "styles", fileName: "main.css"); // Assert Assert.Equal(themePath, @"themes/default/styles/main.css"); } [Fact] public void GetResourcePathReturnsNullIfDirectoryDoesNotExist() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css"))); themesImpl.Initialize("themes", "default"); // Act themesImpl.CurrentTheme = "mobile"; var themePath = themesImpl.GetResourcePath(folder: "does-not-exist", fileName: "main.css"); // Assert Assert.Null(themePath); } [Fact] public void GetResourcePathReturnsNullIfItemNotFoundInCurrentAndDefaultThemeDirectories() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir(@"mobile\styles", "wp7.css"), new Dir(@"default\styles", "main.css"))); themesImpl.Initialize("themes", "default"); // Act themesImpl.CurrentTheme = "mobile"; var themePath = themesImpl.GetResourcePath(folder: "styles", fileName: "awesome-blinking-text.css"); // Assert Assert.Null(themePath); } [Fact] public void AvaliableThemesReturnsTopLevelDirectoriesUnderThemeDirectory() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("mobile"), new Dir("rotary-phone"))); // Act themesImpl.Initialize("themes", "default"); var themes = themesImpl.AvailableThemes; // Assert Assert.Equal(3, themes.Count); Assert.Equal(themes[0], "default"); Assert.Equal(themes[1], "mobile"); Assert.Equal(themes[2], "rotary-phone"); } /// /// // folder structure: /// // /root /// // /foo /// // /bar.cs /// // testing that a file specified as foo/bar in folder root will return null /// [Fact] public void FileWithSlash_ReturnsNull() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("root"), new Dir(@"root\foo", "wp7.css"), new Dir(@"default\styles", "main.css"))); // Act var actual = themesImpl.FindMatchingFile("root", "foo/bar.cs"); // Assert Assert.Null(actual); } [Fact] public void DirectoryWithNoFilesReturnsNull() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir("default"), new Dir("empty-dir"))); // Act var actual = themesImpl.FindMatchingFile(@"themes\empty-dir", "main.css"); // Assert Assert.Null(actual); } [Fact] public void MatchingFiles_ReturnsCorrectFile() { // Arrange var themesImpl = new ThemesImplementation(scopeStorage: new ScopeStorageDictionary(), vpp: GetVirtualPathProvider("themes", new Dir(@"nomatchingfiles", "foo.cs"))); // Act var bar = themesImpl.FindMatchingFile(@"themes\nomatchingfiles", "bar.cs"); var foo = themesImpl.FindMatchingFile(@"themes\nomatchingfiles", "foo.cs"); // Assert Assert.Null(bar); Assert.Equal(@"themes/nomatchingfiles/foo.cs", foo); } private static VirtualPathProvider GetVirtualPathProvider(string themeRoot, params Dir[] fileSystem) { var mockVpp = new Mock(); var dirRoot = new Mock(themeRoot); var themeDirectories = new List(); foreach (var directory in fileSystem) { var dir = new Mock(directory.Name); var directoryPath = themeRoot + '\\' + directory.Name; dir.SetupGet(d => d.Name).Returns(directory.Name); mockVpp.Setup(c => c.GetDirectory(It.Is(p => p.Equals(directoryPath, StringComparison.OrdinalIgnoreCase)))).Returns(dir.Object); var fileList = new List(); foreach (var item in directory.Files) { var filePath = directoryPath + '\\' + item; var file = new Mock(filePath); file.SetupGet(f => f.Name).Returns(item); fileList.Add(file.Object); } dir.SetupGet(c => c.Files).Returns(fileList); themeDirectories.Add(dir.Object); } dirRoot.SetupGet(c => c.Directories).Returns(themeDirectories); mockVpp.Setup(c => c.GetDirectory(themeRoot)).Returns(dirRoot.Object); return mockVpp.Object; } private class Dir { public Dir(string name, params string[] files) { Name = name; Files = files; } public string Name { get; private set; } public IEnumerable Files { get; private set; } } } }