// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Linq.Expressions;
using Moq;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;

namespace System.Web.Mvc.Html.Test
{
    public class LabelExtensionsTest
    {
        Mock<ModelMetadataProvider> metadataProvider;
        Mock<ModelMetadata> metadata;
        ViewDataDictionary viewData;
        Mock<ViewContext> viewContext;
        Mock<IViewDataContainer> viewDataContainer;
        HtmlHelper<object> html;

        public LabelExtensionsTest()
        {
            metadataProvider = new Mock<ModelMetadataProvider>();
            metadata = new Mock<ModelMetadata>(metadataProvider.Object, null, null, typeof(object), null);
            viewData = new ViewDataDictionary();

            viewContext = new Mock<ViewContext>();
            viewContext.Setup(c => c.ViewData).Returns(viewData);

            viewDataContainer = new Mock<IViewDataContainer>();
            viewDataContainer.Setup(c => c.ViewData).Returns(viewData);

            html = new HtmlHelper<object>(viewContext.Object, viewDataContainer.Object);

            metadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny<object>(), It.IsAny<Type>()))
                .Returns(new ModelMetadata[0]);
            metadataProvider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), It.IsAny<Type>(), It.IsAny<string>()))
                .Returns(metadata.Object);
            metadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
                .Returns(metadata.Object);
        }

        // Label tests

        [Fact]
        public void LabelNullExpressionThrows()
        {
            // Act & Assert
            Assert.ThrowsArgumentNull(
                () => html.Label(null),
                "expression");
        }

        [Fact]
        public void LabelViewDataNotFound()
        {
            // Act
            MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""PropertyName"">PropertyName</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelViewDataNull()
        {
            // Act
            viewData["PropertyName"] = null;
            MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""PropertyName"">PropertyName</label>", result.ToHtmlString());
        }

        class Model
        {
            public string PropertyName { get; set; }
        }

        [Fact]
        public void LabelViewDataFromPropertyGetsActualPropertyType()
        {
            // Arrange
            Model model = new Model { PropertyName = "propertyValue" };
            HtmlHelper<Model> html = new HtmlHelper<Model>(viewContext.Object, viewDataContainer.Object);
            viewData.Model = model;
            metadataProvider.Setup(p => p.GetMetadataForProperty(It.IsAny<Func<object>>(), typeof(Model), "PropertyName"))
                .Returns(metadata.Object)
                .Verifiable();

            // Act
            html.Label("PropertyName", null, null, metadataProvider.Object);

            // Assert
            metadataProvider.Verify();
        }

        [Fact]
        public void LabelUsesTemplateInfoPrefix()
        {
            // Arrange
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";

            // Act
            MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""Prefix_PropertyName"">PropertyName</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelUsesLabelTextBeforeMetadata()
        {
            // Arrange
            metadata = new Mock<ModelMetadata>(metadataProvider.Object, null, null, typeof(object), "Custom property name from metadata");
            metadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
                .Returns(metadata.Object);

            //Act
            MvcHtmlString result = html.Label("PropertyName", "Label Text", null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""PropertyName"">Label Text</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelUsesMetadataForDisplayTextWhenLabelTextIsNull()
        {
            // Arrange
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");

            // Act
            MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""PropertyName"">Custom display name from metadata</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelUsesMetadataForPropertyNameWhenDisplayNameIsNull()
        {
            // Arrange
            metadata = new Mock<ModelMetadata>(metadataProvider.Object, null, null, typeof(object), "Custom property name from metadata");
            metadataProvider.Setup(p => p.GetMetadataForType(It.IsAny<Func<object>>(), It.IsAny<Type>()))
                .Returns(metadata.Object);

            // Act
            MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""PropertyName"">Custom property name from metadata</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelEmptyDisplayNameReturnsEmptyLabelText()
        {
            // Arrange
            metadata.Setup(m => m.DisplayName).Returns(String.Empty);

            // Act
            MvcHtmlString result = html.Label("PropertyName", null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(String.Empty, result.ToHtmlString());
        }

        [Fact]
        public void LabelWithAnonymousValues()
        {
            // Act
            MvcHtmlString result = html.Label("PropertyName", null, new { @for = "attrFor" }, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""attrFor"">PropertyName</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelWithAnonymousValuesAndLabelText()
        {
            // Act
            MvcHtmlString result = html.Label("PropertyName", "Label Text", new { @for = "attrFor" }, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""attrFor"">Label Text</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelWithTypedAttributes()
        {
            // Arrange
            Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
            {
                { "foo", "bar" },
                { "quux", "baz" }
            };

            // Act
            MvcHtmlString result = html.Label("PropertyName", null, htmlAttributes, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label foo=""bar"" for=""PropertyName"" quux=""baz"">PropertyName</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelWithTypedAttributesAndLabelText()
        {
            // Arrange
            Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
            {
                { "foo", "bar" },
                { "quux", "baz" }
            };

            // Act
            MvcHtmlString result = html.Label("PropertyName", "Label Text", htmlAttributes, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label foo=""bar"" for=""PropertyName"" quux=""baz"">Label Text</label>", result.ToHtmlString());
        }

        // LabelFor tests

        [Fact]
        public void LabelForNullExpressionThrows()
        {
            // Act & Assert
            Assert.ThrowsArgumentNull(
                () => html.LabelFor((Expression<Func<Object, Object>>)null),
                "expression");
        }

        [Fact]
        public void LabelForNonMemberExpressionThrows()
        {
            // Act & Assert
            Assert.Throws<InvalidOperationException>(
                () => html.LabelFor(model => new { foo = "Bar" }, null, metadataProvider.Object),
                "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.");
        }

        [Fact]
        public void LabelForViewDataNotFound()
        {
            // Arrange
            string unknownKey = "this is a dummy parameter value";

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""unknownKey"">unknownKey</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForUsesTemplateInfoPrefix()
        {
            // Arrange
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
            string unknownKey = "this is a dummy parameter value";

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""Prefix_unknownKey"">unknownKey</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForUsesLabelTextBeforeModelMetadata()
        {
            // Arrange
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
            string unknownKey = "this is a dummy parameter value";

            //Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, "Label Text", null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""unknownKey"">Label Text</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForUsesModelMetadata()
        {
            // Arrange
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");
            string unknownKey = "this is a dummy parameter value";

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""unknownKey"">Custom display name from metadata</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForEmptyDisplayNameReturnsEmptyLabelText()
        {
            // Arrange
            metadata.Setup(m => m.DisplayName).Returns(String.Empty);
            string unknownKey = "this is a dummy parameter value";

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(String.Empty, result.ToHtmlString());
        }

        [Fact]
        public void LabelForWithAnonymousValues()
        {
            //Arrange
            string unknownKey = "this is a dummy parameter value";

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, null, new { @for = "attrFor" }, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""attrFor"">unknownKey</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForWithAnonymousValuesAndLabelText()
        {
            //Arrange
            string unknownKey = "this is a dummy parameter value";

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, "Label Text", new { @for = "attrFor" }, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""attrFor"">Label Text</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForWithTypedAttributes()
        {
            //Arrange
            string unknownKey = "this is a dummy parameter value";

            Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
            {
                { "foo", "bar" },
                { "quux", "baz" }
            };

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, null, htmlAttributes, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label foo=""bar"" for=""unknownKey"" quux=""baz"">unknownKey</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForWithTypedAttributesAndLabelText()
        {
            //Arrange
            string unknownKey = "this is a dummy parameter value";

            Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
            {
                { "foo", "bar" },
                { "quux", "baz" }
            };

            // Act
            MvcHtmlString result = html.LabelFor(model => unknownKey, "Label Text", htmlAttributes, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label foo=""bar"" for=""unknownKey"" quux=""baz"">Label Text</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForWithNestedClass()
        { // Dev10 Bug #936323
            // Arrange
            HtmlHelper<NestedProduct> html = new HtmlHelper<NestedProduct>(viewContext.Object, viewDataContainer.Object);

            // Act
            MvcHtmlString result = html.LabelFor(nested => nested.product.Id, null, null, metadataProvider.Object);

            //Assert
            Assert.Equal(@"<label for=""product_Id"">Id</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForWithArrayExpression()
        { // Dev10 Bug #905780
            // Arrange
            HtmlHelper<Cart> html = new HtmlHelper<Cart>(viewContext.Object, viewDataContainer.Object);

            // Act
            MvcHtmlString result = html.LabelFor(cart => cart.Products[0].Id, null, null, metadataProvider.Object);

            // Assert
            Assert.Equal(@"<label for=""Products_0__Id"">Id</label>", result.ToHtmlString());
        }

        private class Product
        {
            public int Id { get; set; }
        }

        private class Cart
        {
            public Product[] Products { get; set; }
        }

        private class NestedProduct
        {
            public Product product = new Product();
        }

        // LabelForModel tests

        [Fact]
        public void LabelForModelUsesLabelTextBeforeModelMetadata()
        {
            // Arrange
            viewData.ModelMetadata = metadata.Object;
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");

            // Act
            MvcHtmlString result = html.LabelForModel("Label Text");

            // Assert
            Assert.Equal(@"<label for=""Prefix"">Label Text</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForModelUsesModelMetadata()
        {
            // Arrange
            viewData.ModelMetadata = metadata.Object;
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");

            // Act
            MvcHtmlString result = html.LabelForModel();

            // Assert
            Assert.Equal(@"<label for=""Prefix"">Custom display name from metadata</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForModelWithAnonymousValues()
        {
            //Arrange
            viewData.ModelMetadata = metadata.Object;
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");

            // Act
            MvcHtmlString result = html.LabelForModel(new { @for = "attrFor" });

            // Assert
            Assert.Equal(@"<label for=""attrFor"">Custom display name from metadata</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForModelWithAnonymousValuesAndLabelText()
        {
            //Arrange
            viewData.ModelMetadata = metadata.Object;
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");

            // Act
            MvcHtmlString result = html.LabelForModel("Label Text", new { @for = "attrFor" });

            // Assert
            Assert.Equal(@"<label for=""attrFor"">Label Text</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForModelWithTypedAttributes()
        {
            //Arrange
            viewData.ModelMetadata = metadata.Object;
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");

            Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
            {
                { "foo", "bar" },
                { "quux", "baz" }
            };

            // Act
            MvcHtmlString result = html.LabelForModel(htmlAttributes);

            // Assert
            Assert.Equal(@"<label foo=""bar"" for=""Prefix"" quux=""baz"">Custom display name from metadata</label>", result.ToHtmlString());
        }

        [Fact]
        public void LabelForModelWithTypedAttributesAndLabelText()
        {
            //Arrange
            viewData.ModelMetadata = metadata.Object;
            viewData.TemplateInfo.HtmlFieldPrefix = "Prefix";
            metadata.Setup(m => m.DisplayName).Returns("Custom display name from metadata");

            Dictionary<string, object> htmlAttributes = new Dictionary<string, object>
            {
                { "foo", "bar" },
                { "quux", "baz" }
            };

            // Act
            MvcHtmlString result = html.LabelForModel("Label Text", htmlAttributes);

            // Assert
            Assert.Equal(@"<label foo=""bar"" for=""Prefix"" quux=""baz"">Label Text</label>", result.ToHtmlString());
        }
    }
}