|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers.Testing;
using Microsoft.AspNetCore.InternalTesting;
using Moq;
namespace Microsoft.AspNetCore.Mvc.TagHelpers;
public class InputTagHelperTest
{
public static TheoryData<TagHelperAttributeList, string> MultiAttributeCheckBoxData
{
get
{
// outputAttributes, expectedAttributeString
return new TheoryData<TagHelperAttributeList, string>
{
{
new TagHelperAttributeList
{
{ "hello", "world" },
{ "hello", "world2" }
},
"hello=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\""
},
{
new TagHelperAttributeList
{
{ "hello", "world" },
{ "hello", "world2" },
{ "hello", "world3" }
},
"hello=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\" hello=\"HtmlEncode[[world3]]\""
},
{
new TagHelperAttributeList
{
{ "HelLO", "world" },
{ "HELLO", "world2" }
},
"HelLO=\"HtmlEncode[[world]]\" HELLO=\"HtmlEncode[[world2]]\""
},
{
new TagHelperAttributeList
{
{ "Hello", "world" },
{ "HELLO", "world2" },
{ "hello", "world3" }
},
"Hello=\"HtmlEncode[[world]]\" HELLO=\"HtmlEncode[[world2]]\" hello=\"HtmlEncode[[world3]]\""
},
{
new TagHelperAttributeList
{
{ "HeLlO", "world" },
{ "hello", "world2" }
},
"HeLlO=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\""
},
};
}
}
[Theory]
[MemberData(nameof(MultiAttributeCheckBoxData))]
public async Task CheckBoxHandlesMultipleAttributesSameNameArePreserved(
TagHelperAttributeList outputAttributes,
string expectedAttributeString)
{
// Arrange
var originalContent = "original content";
var expectedContent = $"<input {expectedAttributeString} type=\"HtmlEncode[[checkbox]]\" id=\"HtmlEncode[[IsACar]]\" " +
$"name=\"HtmlEncode[[IsACar]]\" value=\"HtmlEncode[[true]]\" />" +
"<input name=\"HtmlEncode[[IsACar]]\" type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"input",
outputAttributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(originalContent);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: false, propertyName: nameof(Model.IsACar));
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.NotNull(output.PostElement);
Assert.Equal(originalContent, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output));
}
[Theory]
[InlineData("bad")]
[InlineData("notbool")]
public void CheckBoxHandlesNonParsableStringsAsBoolsCorrectly(
string possibleBool)
{
// Arrange
const string content = "original content";
const string tagName = "input";
const string forAttributeName = "asp-for";
var expected = Resources.FormatInputTagHelper_InvalidStringResult(
forAttributeName,
possibleBool,
typeof(bool).FullName);
var attributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(content);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: possibleBool, propertyName: nameof(Model.IsACar));
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => tagHelper.Process(context, output));
Assert.Equal(expected, ex.Message);
}
[Theory]
[InlineData(10)]
[InlineData(1337)]
public void CheckBoxHandlesInvalidDataTypesCorrectly(
int possibleBool)
{
// Arrange
const string content = "original content";
const string tagName = "input";
const string forAttributeName = "asp-for";
var expected = Resources.FormatInputTagHelper_InvalidExpressionResult(
"<input>",
forAttributeName,
possibleBool.GetType().FullName,
typeof(bool).FullName,
typeof(string).FullName,
"type",
"checkbox");
var attributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(content);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: possibleBool, propertyName: nameof(Model.IsACar));
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => tagHelper.Process(context, output));
Assert.Equal(expected, ex.Message);
}
[Theory]
[InlineData("trUE")]
[InlineData("FAlse")]
public void CheckBoxHandlesParsableStringsAsBoolsCorrectly(
string possibleBool)
{
// Arrange
const string content = "original content";
const string tagName = "input";
const string isCheckedAttr = "checked=\"HtmlEncode[[checked]]\" ";
var isChecked = (bool.Parse(possibleBool) ? isCheckedAttr : string.Empty);
var expectedContent = $"<input class=\"HtmlEncode[[form-control]]\" type=\"HtmlEncode[[checkbox]]\" " +
$"{isChecked}id=\"HtmlEncode[[IsACar]]\" name=\"HtmlEncode[[IsACar]]\" " +
"value=\"HtmlEncode[[true]]\" /><input name=\"HtmlEncode[[IsACar]]\" type=\"HtmlEncode[[hidden]]\" " +
"value=\"HtmlEncode[[false]]\" />";
var expectedPostElement = "<input name=\"IsACar\" type=\"hidden\" value=\"false\" />";
var attributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(content);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: possibleBool, propertyName: nameof(Model.IsACar));
// Act
tagHelper.Process(context, output);
// Assert
Assert.Equal(content, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output));
Assert.Equal(expectedPostElement, output.PostElement.GetContent());
}
[Theory]
[InlineData("checkbox")]
[InlineData("hidden")]
[InlineData("number")]
[InlineData("password")]
[InlineData("text")]
public void Process_WithEmptyForName_Throws(string inputTypeName)
{
// Arrange
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "type", inputTypeName },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
"input",
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act & Assert
ExceptionAssert.ThrowsArgument(
() => tagHelper.Process(context, output),
paramName: "expression",
exceptionMessage: expectedMessage);
}
[Fact]
public void Process_Radio_WithEmptyForName_Throws()
{
// Arrange
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
var inputTypeName = "radio";
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = 23;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(int), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Value = "24",
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "type", inputTypeName },
{ "value", "24" },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
"input",
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act & Assert
ExceptionAssert.ThrowsArgument(
() => tagHelper.Process(context, output),
paramName: "expression",
exceptionMessage: expectedMessage);
}
[Theory]
[InlineData("hidden")]
[InlineData("number")]
[InlineData("text")]
public void Process_WithEmptyForName_DoesNotThrow_WithName(string inputTypeName)
{
// Arrange
var expectedAttributeValue = "-expression-";
var expectedTagName = "input";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", expectedAttributeValue },
{ "type", inputTypeName },
{ "value", "False" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.ClientValidationEnabled = false;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = expectedAttributeValue,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", expectedAttributeValue },
{ "type", inputTypeName },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
tagHelper.Process(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public void Process_Checkbox_WithEmptyForName_DoesNotThrow_WithName()
{
// Arrange
var expectedAttributeValue = "-expression-";
var expectedPostElementContent = $"<input name=\"HtmlEncode[[{expectedAttributeValue}]]\" " +
"type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
var expectedTagName = "input";
var inputTypeName = "checkbox";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", expectedAttributeValue },
{ "type", inputTypeName },
{ "value", "true" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.ClientValidationEnabled = false;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = expectedAttributeValue,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", expectedAttributeValue },
{ "type", inputTypeName },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
tagHelper.Process(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
Assert.False(viewContext.FormContext.HasEndOfFormContent);
Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement));
}
[Fact]
public void Process_Password_WithEmptyForName_DoesNotThrow_WithName()
{
// Arrange
var expectedAttributeValue = "-expression-";
var expectedTagName = "input";
var inputTypeName = "password";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", expectedAttributeValue },
{ "type", inputTypeName },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = "password";
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.ClientValidationEnabled = false;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = expectedAttributeValue,
ViewContext = viewContext,
};
// Expect attributes to just pass through. Tag helper binds all input attributes and doesn't add any.
var context = new TagHelperContext(expectedAttributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
tagHelper.Process(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public void Process_Radio_WithEmptyForName_DoesNotThrow_WithName()
{
// Arrange
var expectedAttributeValue = "-expression-";
var expectedTagName = "input";
var inputTypeName = "radio";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", expectedAttributeValue },
{ "type", inputTypeName },
{ "value", "24" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = 23;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(int), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.ClientValidationEnabled = false;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = expectedAttributeValue,
Value = "24",
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", expectedAttributeValue },
{ "type", inputTypeName },
{ "value", "24" },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
tagHelper.Process(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
}
// Top-level container (List<Model> or Model instance), immediate container type (Model or NestModel),
// model accessor, expression path / id, expected value.
public static TheoryData<object, Type, object, NameAndId, string> TestDataSet
{
get
{
var modelWithNull = new Model
{
NestedModel = new NestedModel
{
Text = null,
},
Text = null,
};
var modelWithText = new Model
{
NestedModel = new NestedModel
{
Text = "inner text",
},
Text = "outer text",
};
var models = new List<Model>
{
modelWithNull,
modelWithText,
};
return new TheoryData<object, Type, object, NameAndId, string>
{
{ null, typeof(Model), null, new NameAndId("Text", "Text"),
string.Empty },
{ modelWithNull, typeof(Model), modelWithNull.Text, new NameAndId("Text", "Text"),
string.Empty },
{ modelWithText, typeof(Model), modelWithText.Text, new NameAndId("Text", "Text"),
"outer text" },
{ modelWithNull, typeof(NestedModel), modelWithNull.NestedModel.Text,
new NameAndId("NestedModel.Text", "NestedModel_Text"), string.Empty },
{ modelWithText, typeof(NestedModel), modelWithText.NestedModel.Text,
new NameAndId("NestedModel.Text", "NestedModel_Text"), "inner text" },
{ models, typeof(Model), models[0].Text,
new NameAndId("[0].Text", "z0__Text"), string.Empty },
{ models, typeof(Model), models[1].Text,
new NameAndId("[1].Text", "z1__Text"), "outer text" },
{ models, typeof(NestedModel), models[0].NestedModel.Text,
new NameAndId("[0].NestedModel.Text", "z0__NestedModel_Text"), string.Empty },
{ models, typeof(NestedModel), models[1].NestedModel.Text,
new NameAndId("[1].NestedModel.Text", "z1__NestedModel_Text"), "inner text" },
};
}
}
[Theory]
[MemberData(nameof(TestDataSet))]
public async Task ProcessAsync_GeneratesExpectedOutput(
object container,
Type containerType,
object model,
NameAndId nameAndId,
string expectedValue)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
{ "type", "text" },
{ "id", nameAndId.Id },
{ "name", nameAndId.Name },
{ "valid", "from validation attributes" },
{ "value", expectedValue },
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var expectedPostContent = "original post-content";
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var originalAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var output = new TagHelperOutput(
expectedTagName,
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
})
{
TagMode = TagMode.StartTagOnly,
};
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent(expectedPostContent);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider())
{
ValidationAttributes =
{
{ "valid", "from validation attributes" },
}
};
// Property name is either nameof(Model.Text) or nameof(NestedModel.Text).
var tagHelper = GetTagHelper(
htmlGenerator,
container,
containerType,
model,
propertyName: nameof(Model.Text),
expressionName: nameAndId.Name);
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(TagMode.StartTagOnly, output.TagMode);
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]
[InlineData("datetime", "datetime")]
[InlineData(null, "text")]
[InlineData("hidden", "hidden")]
public void Process_GeneratesFormattedOutput(string specifiedType, string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", "DateTimeOffset" },
{ "name", "DateTimeOffset" },
{ "valid", "from validation attributes" },
{ "value", "datetime: 2011-08-31T05:30:45.0000000+03:00" },
};
var expectedTagName = "not-input";
var container = new Model
{
DateTimeOffset = new DateTimeOffset(2011, 8, 31, hour: 5, minute: 30, second: 45, offset: TimeSpan.FromHours(3))
};
var allAttributes = new TagHelperAttributeList
{
{ "type", specifiedType },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: allAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
throw new Exception("getChildContentAsync should not be called.");
})
{
TagMode = TagMode.StartTagOnly,
};
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider())
{
ValidationAttributes =
{
{ "valid", "from validation attributes" },
}
};
var tagHelper = GetTagHelper(
htmlGenerator,
container,
typeof(Model),
model: container.DateTimeOffset,
propertyName: nameof(Model.DateTimeOffset),
expressionName: nameof(Model.DateTimeOffset));
tagHelper.Format = "datetime: {0:o}";
tagHelper.InputTypeName = specifiedType;
// Act
tagHelper.Process(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Empty(output.PreContent.GetContent());
Assert.Empty(output.Content.GetContent());
Assert.Empty(output.PostContent.GetContent());
Assert.Equal(TagMode.StartTagOnly, output.TagMode);
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]
[InlineData("datetime", "datetime")]
[InlineData(null, "datetime-local")]
[InlineData("hidden", "hidden")]
public void Process_GeneratesFormattedOutput_ForDateTime(string specifiedType, string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", nameof(Model.DateTime) },
{ "name", nameof(Model.DateTime) },
{ "valid", "from validation attributes" },
{ "value", "datetime: 2011-08-31T05:30:45.0000000Z" },
};
var expectedTagName = "not-input";
var container = new Model
{
DateTime = new DateTime(2011, 8, 31, hour: 5, minute: 30, second: 45, kind: DateTimeKind.Utc),
};
var allAttributes = new TagHelperAttributeList
{
{ "type", specifiedType },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: allAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
throw new Exception("getChildContentAsync should not be called.");
})
{
TagMode = TagMode.StartTagOnly,
};
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider())
{
ValidationAttributes =
{
{ "valid", "from validation attributes" },
}
};
var tagHelper = GetTagHelper(
htmlGenerator,
container,
typeof(Model),
model: container.DateTime,
propertyName: nameof(Model.DateTime),
expressionName: nameof(Model.DateTime));
tagHelper.Format = "datetime: {0:o}";
tagHelper.InputTypeName = specifiedType;
// Act
tagHelper.Process(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Empty(output.PreContent.GetContent());
Assert.Empty(output.Content.GetContent());
Assert.Empty(output.PostContent.GetContent());
Assert.Equal(TagMode.StartTagOnly, output.TagMode);
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]
[InlineData("SomeProperty", "SomeProperty", true)]
[InlineData("SomeProperty", "[0].SomeProperty", true)]
[InlineData("SomeProperty", "[0].SomeProperty", false)]
public void Process_GeneratesInvariantCultureMetadataInput_WhenValueUsesInvariantFormatting(string propertyName, string nameAttributeValue, bool usesInvariantFormatting)
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator.Object, metadataProvider);
var tagHelper = new InputTagHelper(htmlGenerator.Object)
{
For = modelExpression,
InputTypeName = "text",
Name = propertyName,
ViewContext = viewContext,
};
var tagBuilder = new TagBuilder("input")
{
TagRenderMode = TagRenderMode.SelfClosing,
Attributes =
{
{ "name", nameAttributeValue },
},
};
htmlGenerator
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
modelExplorer.Model,
null, // format
It.IsAny<object>())) // htmlAttributes
.Returns(tagBuilder)
.Callback(() => viewContext.FormContext.InvariantField(tagBuilder.Attributes["name"], usesInvariantFormatting))
.Verifiable();
var expectedPostElement = usesInvariantFormatting
? $"<input name=\"__Invariant\" type=\"hidden\" value=\"{tagBuilder.Attributes["name"]}\" />"
: string.Empty;
var attributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", "text" },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
"input",
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
tagHelper.Process(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(expectedPostElement, output.PostElement.GetContent());
}
[Fact]
public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeNone()
{
var propertyName = "-expression-";
var expectedTagName = "input";
var inputTypeName = "checkbox";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
{ "value", "true" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = propertyName,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
Assert.False(viewContext.FormContext.HasEndOfFormContent);
Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement)));
}
[Fact]
public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeInline()
{
var propertyName = "-expression-";
var expectedTagName = "input";
var expectedPostElementContent = $"<input name=\"HtmlEncode[[{propertyName}]]\" " +
"type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
var inputTypeName = "checkbox";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
{ "value", "true" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.FormContext.CanRenderAtEndOfForm = true;
viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.Inline;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = propertyName,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
Assert.False(viewContext.FormContext.HasEndOfFormContent);
Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement));
}
[Fact]
public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeEndOfForm()
{
var propertyName = "-expression-";
var expectedTagName = "input";
var expectedEndOfFormContent = $"<input name=\"HtmlEncode[[{propertyName}]]\" " +
"type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
var inputTypeName = "checkbox";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
{ "value", "true" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.FormContext.CanRenderAtEndOfForm = true;
viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = propertyName,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
Assert.Equal(expectedEndOfFormContent, string.Join("", viewContext.FormContext.EndOfFormContent.Select(html => HtmlContentUtilities.HtmlContentToString(html))));
Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement)));
}
[Fact]
public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeEndOfForm_AndCanRenderAtEndOfFormNotSet()
{
var propertyName = "-expression-";
var expectedTagName = "input";
var expectedPostElementContent = $"<input name=\"HtmlEncode[[{propertyName}]]\" " +
"type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
var inputTypeName = "checkbox";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
{ "value", "true" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.FormContext.CanRenderAtEndOfForm = false;
viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = propertyName,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
Assert.False(viewContext.FormContext.HasEndOfFormContent);
Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement));
}
[Fact]
public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters()
{
// Arrange
var originalContent = "original content";
var expectedPreContent = "original pre-content";
var expectedContent = "<input class=\"HtmlEncode[[form-control]]\" type=\"HtmlEncode[[checkbox]]\" /><hidden />";
var expectedPostContent = "original post-content";
var expectedPostElement = "<hidden />";
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var originalAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var output = new TagHelperOutput(
"input",
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
})
{
TagMode = TagMode.SelfClosing,
};
output.PreContent.AppendHtml(expectedPreContent);
output.Content.AppendHtml(originalContent);
output.PostContent.AppendHtml(expectedPostContent);
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(htmlGenerator.Object, model: false, propertyName: nameof(Model.IsACar));
tagHelper.Format = "somewhat-less-null"; // ignored
var tagBuilder = new TagBuilder("input")
{
TagRenderMode = TagRenderMode.SelfClosing
};
htmlGenerator
.Setup(mock => mock.GenerateCheckBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
null, // isChecked
It.IsAny<object>())) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
htmlGenerator
.Setup(mock => mock.GenerateHiddenForCheckbox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name))
.Returns(new TagBuilder("hidden") { TagRenderMode = TagRenderMode.SelfClosing })
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.NotEmpty(output.Attributes);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(originalContent, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output));
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(expectedPostElement, output.PostElement.GetContent());
Assert.Equal(TagMode.SelfClosing, output.TagMode);
}
[Theory]
[InlineData(null, "hidden", null, null)]
[InlineData(null, "Hidden", "not-null", "somewhat-less-null")]
[InlineData(null, "HIDden", null, "somewhat-less-null")]
[InlineData(null, "HIDDEN", "not-null", null)]
[InlineData("hiddeninput", null, null, null)]
[InlineData("HiddenInput", null, "not-null", null)]
[InlineData("hidDENinPUT", null, null, "somewhat-less-null")]
[InlineData("HIDDENINPUT", null, "not-null", "somewhat-less-null")]
public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParametersForHidden(
string dataTypeName,
string inputTypeName,
string model,
string format)
{
// Arrange
var contextAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
if (!string.IsNullOrEmpty(inputTypeName))
{
contextAttributes.SetAttribute("type", inputTypeName); // Support restoration of type attribute, if any.
}
var expectedAttributes = new TagHelperAttributeList
{
{ "class", "form-control hidden-control" },
{ "type", inputTypeName ?? "hidden" }, // Generator restores type attribute; adds "hidden" if none.
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var expectedPostContent = "original post-content";
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: contextAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test");
var originalAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var output = new TagHelperOutput(
expectedTagName,
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
})
{
TagMode = TagMode.StartTagOnly,
};
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent(expectedPostContent);
var metadataProvider = new TestModelMetadataProvider();
metadataProvider.ForProperty<Model>("Text").DisplayDetails(dd => dd.DataTypeName = dataTypeName);
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(
htmlGenerator.Object,
model,
nameof(Model.Text),
metadataProvider: metadataProvider);
tagHelper.Format = format;
tagHelper.InputTypeName = inputTypeName;
var tagBuilder = new TagBuilder("input")
{
Attributes =
{
{ "class", "hidden-control" },
},
};
htmlGenerator
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
model, // value
format,
new Dictionary<string, object> { { "type", "hidden" } })) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.StartTagOnly, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes, CaseSensitiveTagHelperAttributeComparer.Default);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputExpectedFormAttribute()
{
var propertyName = "-expression-";
var expectedTagName = "input";
var formName = "form-name";
var expectedEndOfFormContent = $"<input form=\"HtmlEncode[[{formName}]]\" name=\"HtmlEncode[[{propertyName}]]\" " +
"type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
var inputTypeName = "checkbox";
var expectedAttributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
{ "form", formName },
{ "value", "true" },
};
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var model = false;
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
viewContext.FormContext.CanRenderAtEndOfForm = true;
viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm;
var tagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
InputTypeName = inputTypeName,
Name = propertyName,
FormName = formName,
ViewContext = viewContext,
};
var attributes = new TagHelperAttributeList
{
{ "name", propertyName },
{ "type", inputTypeName },
{ "form", formName}
};
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
var output = new TagHelperOutput(
expectedTagName,
new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.False(output.IsContentModified);
Assert.Equal(expectedTagName, output.TagName);
Assert.Equal(expectedEndOfFormContent, string.Join("", viewContext.FormContext.EndOfFormContent.Select(html => HtmlContentUtilities.HtmlContentToString(html))));
Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement)));
}
[Theory]
[InlineData(null, "password", null)]
[InlineData(null, "Password", "not-null")]
[InlineData(null, "PASSword", null)]
[InlineData(null, "PASSWORD", "not-null")]
[InlineData("password", null, null)]
[InlineData("Password", null, "not-null")]
[InlineData("PASSword", null, null)]
[InlineData("PASSWORD", null, "not-null")]
public async Task ProcessAsync_CallsGeneratePassword_WithExpectedParameters(
string dataTypeName,
string inputTypeName,
string model)
{
// Arrange
var contextAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
if (!string.IsNullOrEmpty(inputTypeName))
{
contextAttributes.SetAttribute("type", inputTypeName); // Support restoration of type attribute, if any.
}
var expectedAttributes = new TagHelperAttributeList
{
{ "class", "form-control password-control" },
{ "type", inputTypeName ?? "password" }, // Generator restores type attribute; adds "password" if none.
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var expectedPostContent = "original post-content";
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: contextAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test");
var originalAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var output = new TagHelperOutput(
expectedTagName,
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
})
{
TagMode = TagMode.StartTagOnly,
};
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent(expectedPostContent);
var metadataProvider = new TestModelMetadataProvider();
metadataProvider.ForProperty<Model>("Text").DisplayDetails(dd => dd.DataTypeName = dataTypeName);
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(
htmlGenerator.Object,
model,
nameof(Model.Text),
metadataProvider: metadataProvider);
tagHelper.Format = "somewhat-less-null"; // ignored
tagHelper.InputTypeName = inputTypeName;
var tagBuilder = new TagBuilder("input")
{
Attributes =
{
{ "class", "password-control" },
},
};
htmlGenerator
.Setup(mock => mock.GeneratePassword(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
null, // value
null)) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.StartTagOnly, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes, CaseSensitiveTagHelperAttributeComparer.Default);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]
[InlineData("radio", null)]
[InlineData("Radio", "not-null")]
[InlineData("RADio", null)]
[InlineData("RADIO", "not-null")]
public async Task ProcessAsync_CallsGenerateRadioButton_WithExpectedParameters(
string inputTypeName,
string model)
{
// Arrange
var value = "match"; // Real generator would use this for comparison with For.Metadata.Model.
var contextAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
{ "value", value },
};
if (!string.IsNullOrEmpty(inputTypeName))
{
contextAttributes.SetAttribute("type", inputTypeName); // Support restoration of type attribute, if any.
}
var expectedAttributes = new TagHelperAttributeList
{
{ "class", "form-control radio-control" },
{ "value", value },
{ "type", inputTypeName ?? "radio" }, // Generator restores type attribute; adds "radio" if none.
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var expectedPostContent = "original post-content";
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: contextAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test");
var originalAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var output = new TagHelperOutput(
expectedTagName,
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
})
{
TagMode = TagMode.StartTagOnly,
};
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent(expectedPostContent);
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(htmlGenerator.Object, model, nameof(Model.Text));
tagHelper.Format = "somewhat-less-null"; // ignored
tagHelper.InputTypeName = inputTypeName;
tagHelper.Value = value;
var tagBuilder = new TagBuilder("input")
{
Attributes =
{
{ "class", "radio-control" },
},
};
htmlGenerator
.Setup(mock => mock.GenerateRadioButton(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
value,
null, // isChecked
null)) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.StartTagOnly, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes, CaseSensitiveTagHelperAttributeComparer.Default);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]
[InlineData(null, null, null, "somewhat-less-null")]
[InlineData(null, null, "not-null", null)]
[InlineData(null, "string", null, null)]
[InlineData(null, "String", "not-null", null)]
[InlineData(null, "STRing", null, "somewhat-less-null")]
[InlineData(null, "STRING", "not-null", null)]
[InlineData(null, "text", null, null)]
[InlineData(null, "Text", "not-null", "somewhat-less-null")]
[InlineData(null, "TExt", null, null)]
[InlineData(null, "TEXT", "not-null", null)]
[InlineData("string", null, null, null)]
[InlineData("String", null, "not-null", null)]
[InlineData("STRing", null, null, null)]
[InlineData("STRING", null, "not-null", null)]
[InlineData("text", null, null, null)]
[InlineData("Text", null, "not-null", null)]
[InlineData("TExt", null, null, null)]
[InlineData("TEXT", null, "not-null", null)]
[InlineData("custom-datatype", null, null, null)]
[InlineData(null, "unknown-input-type", "not-null", null)]
[InlineData("Image", null, "not-null", "somewhat-less-null")]
[InlineData(null, "image", "not-null", null)]
public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParameters(
string dataTypeName,
string inputTypeName,
string model,
string format)
{
// Arrange
var contextAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
if (!string.IsNullOrEmpty(inputTypeName))
{
contextAttributes.SetAttribute("type", inputTypeName); // Support restoration of type attribute, if any.
}
var expectedAttributes = new TagHelperAttributeList
{
{ "class", "form-control text-control" },
{ "type", inputTypeName ?? "text" }, // Generator restores type attribute; adds "text" if none.
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var expectedPostContent = "original post-content";
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: contextAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test");
var originalAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var output = new TagHelperOutput(
expectedTagName,
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
})
{
TagMode = TagMode.StartTagOnly,
};
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent(expectedPostContent);
var metadataProvider = new TestModelMetadataProvider();
metadataProvider.ForProperty<Model>("Text").DisplayDetails(dd => dd.DataTypeName = dataTypeName);
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(
htmlGenerator.Object,
model,
nameof(Model.Text),
metadataProvider: metadataProvider);
tagHelper.Format = format;
tagHelper.InputTypeName = inputTypeName;
var tagBuilder = new TagBuilder("input")
{
Attributes =
{
{ "class", "text-control" },
},
};
htmlGenerator
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
model, // value
format,
It.Is<Dictionary<string, object>>(m => m.ContainsKey("type")))) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.StartTagOnly, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes, CaseSensitiveTagHelperAttributeComparer.Default);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
public static TheoryData<string, string, string> InputTypeData
{
get
{
return new TheoryData<string, string, string>
{
{ null, null, "text" },
{ "Byte", null, "number" },
{ "custom-datatype", null, "text" },
{ "Custom-Datatype", null, "text" },
{ "date", null, "date" }, // No date/time special cases since ModelType is string.
{ "datetime", null, "datetime-local" },
{ "datetime-local", null, "datetime-local" },
{ "DATETIME-local", null, "datetime-local" },
{ "datetimeOffset", null, "text" },
{ "Decimal", "{0:0.00}"{0:0.00}", "text" },
{ "Double", null, "text" },
{ "Int16", null, "number" },
{ "Int32", null, "number" },
{ "int32", null, "number" },
{ "Int64", null, "number" },
{ "SByte", null, "number" },
{ "Single", null, "text" },
{ "SINGLE", null, "text" },
{ "string", null, "text" },
{ "STRING", null, "text" },
{ "text", null, "text" },
{ "TEXT", null, "text" },
{ "time", null, "time" },
{ "month", "{0:yyyy-MM}", "month" },
{ "week", null, "week" },
{ "UInt16", null, "number" },
{ "uint16", null, "number" },
{ "UInt32", null, "number" },
{ "UInt64", null, "number" },
{ nameof(IFormFile), null, "file" },
{ TemplateRenderer.IEnumerableOfIFormFileName, null, "file" },
};
}
}
[Theory]
[MemberData(nameof(InputTypeData))]
public async Task ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributes(
string dataTypeName,
string expectedFormat,
string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType }, // Calculated; not passed to HtmlGenerator.
};
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var metadataProvider = new TestModelMetadataProvider();
metadataProvider.ForProperty<Model>("Text").DisplayDetails(dd => dd.DataTypeName = dataTypeName);
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(
htmlGenerator.Object,
model: null,
propertyName: nameof(Model.Text),
metadataProvider: metadataProvider);
var tagBuilder = new TagBuilder("input");
var htmlAttributes = new Dictionary<string, object>
{
{ "type", expectedType }
};
if (string.Equals(dataTypeName, TemplateRenderer.IEnumerableOfIFormFileName))
{
htmlAttributes["multiple"] = "multiple";
}
htmlGenerator
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
null, // value
expectedFormat,
htmlAttributes)) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Empty(output.PreContent.GetContent());
Assert.Equal(string.Empty, output.Content.GetContent());
Assert.Empty(output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateTime_RendersAsDateTime()
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", "datetime" }, // Calculated; not passed to HtmlGenerator.
};
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList()
{
{"type", "datetime" }
},
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var htmlAttributes = new Dictionary<string, object>
{
{ "type", "datetime" }
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(
htmlGenerator.Object,
model: null,
propertyName: "DateTime",
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
tagHelper.InputTypeName = "datetime";
var tagBuilder = new TagBuilder("input");
htmlGenerator
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
null, // value
@"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}",
htmlAttributes)) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Empty(output.PreContent.GetContent());
Assert.Equal(string.Empty, output.Content.GetContent());
Assert.Empty(output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateOnly_RendersAsDate()
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", "date" }, // Calculated; not passed to HtmlGenerator.
};
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList()
{
{"type", "date" }
},
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var htmlAttributes = new Dictionary<string, object>
{
{ "type", "date" }
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(
htmlGenerator.Object,
model: null,
propertyName: "DateOnly",
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
tagHelper.InputTypeName = "date";
var tagBuilder = new TagBuilder("input");
htmlGenerator
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
null, // value
@"{0:yyyy-MM-dd}",
htmlAttributes)) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Empty(output.PreContent.GetContent());
Assert.Equal(string.Empty, output.Content.GetContent());
Assert.Empty(output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]
[InlineData("Date", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")] // Format from [DataType].
[InlineData("Date", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
[InlineData("DateTime", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
[InlineData("DateTime", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}", "datetime-local")]
[InlineData("DateOnlyDate", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")] // Format from [DataType].
[InlineData("DateOnlyDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
[InlineData("DateOnly", Html5DateRenderingMode.CurrentCulture, null, "text")] // Format from [DataType].
[InlineData("DateOnly", Html5DateRenderingMode.Rfc3339, null, "text")]
[InlineData("DateTimeOffset", Html5DateRenderingMode.CurrentCulture, null, "text")]
[InlineData("DateTimeOffset", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}", "text")]
[InlineData("DateTimeLocal", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
[InlineData("DateTimeLocal", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}", "datetime-local")]
[InlineData("Time", Html5DateRenderingMode.CurrentCulture, "{0:t}", "time")] // Format from [DataType].
[InlineData("Time", Html5DateRenderingMode.Rfc3339, @"{0:HH\:mm\:ss.fff}", "time")]
[InlineData("Month", Html5DateRenderingMode.CurrentCulture, "{0:yyyy-MM}", "month")]
[InlineData("Month", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM}", "month")]
[InlineData("Week", Html5DateRenderingMode.CurrentCulture, null, "week")]
[InlineData("Week", Html5DateRenderingMode.Rfc3339, null, "week")]
[InlineData("NullableDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
[InlineData("NullableDateTime", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}", "datetime-local")]
[InlineData("NullableDateOnlyDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
[InlineData("NullableDateOnly", Html5DateRenderingMode.Rfc3339, null, "text")]
[InlineData("NullableDateTimeOffset", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}", "text")]
public async Task ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339(
string propertyName,
Html5DateRenderingMode dateRenderingMode,
string expectedFormat,
string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType }, // Calculated; not passed to HtmlGenerator.
};
var expectedTagName = "not-input";
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var htmlAttributes = new Dictionary<string, object>
{
{ "type", expectedType }
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
var tagHelper = GetTagHelper(
htmlGenerator.Object,
model: null,
propertyName: propertyName,
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = dateRenderingMode;
var tagBuilder = new TagBuilder("input");
htmlGenerator
.Setup(mock => mock.GenerateTextBox(
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name,
null, // value
expectedFormat,
htmlAttributes)) // htmlAttributes
.Returns(tagBuilder)
.Verifiable();
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
htmlGenerator.Verify();
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Empty(output.PreContent.GetContent());
Assert.Equal(string.Empty, output.Content.GetContent());
Assert.Empty(output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}
// Html5DateRenderingMode.Rfc3339 is enabled by default.
[Theory]
[InlineData("Date", "2000-01-02", "date")]
[InlineData("DateTime", "2000-01-02T03:04:05.060", "datetime-local")]
[InlineData("DateTimeLocal", "2000-01-02T03:04:05.060", "datetime-local")]
[InlineData("DateTimeOffset", "2000-01-02T03:04:05.060Z", "text")]
[InlineData("Time", "03:04:05.060", "time")]
[InlineData("Month", "2000-01", "month")]
[InlineData("Week", "1999-W52", "week")]
public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateTime(
string propertyName,
string expectedValue,
string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", propertyName },
{ "name", propertyName },
{ "value", expectedValue },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedType,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var model = new DateTime(
year: 2000,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5,
millisecond: 60,
kind: DateTimeKind.Utc);
var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
var tagHelper = GetTagHelper(
htmlGenerator,
model: model,
propertyName: propertyName,
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedType, output.TagName);
}
// Html5DateRenderingMode.Rfc3339 is enabled by default.
[Theory]
[InlineData("DateOnlyDate", "2000-01-02", "date")]
[InlineData("DateOnly", "02/01/2000", "text")]
[ReplaceCulture]
public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateOnly(
string propertyName,
string expectedValue,
string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", propertyName },
{ "name", propertyName },
{ "value", expectedValue },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedType,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var model = new DateOnly(
year: 2000,
month: 1,
day: 2);
var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
var tagHelper = GetTagHelper(
htmlGenerator,
model: model,
propertyName: propertyName,
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedType, output.TagName);
}
// Html5DateRenderingMode.Rfc3339 can be disabled.
[Theory]
[InlineData("Date", null, "02/01/2000", "date")]
[InlineData("Date", "{0:d}", "02/01/2000", "date")]
[InlineData("DateTime", null, "02/01/2000 03:04:05", "datetime-local")]
[InlineData("DateTimeLocal", null, "02/01/2000 03:04:05", "datetime-local")]
[InlineData("DateTimeOffset", null, "02/01/2000 03:04:05", "text")]
[InlineData("DateTimeOffset", "{0:o}", "2000-01-02T03:04:05.0600000Z", "text")]
[InlineData("Time", null, "03:04", "time")]
[InlineData("Time", "{0:t}", "03:04", "time")]
[InlineData("Month", null, "2000-01", "month")]
[InlineData("Month", "{0:yyyy/MM}", "2000/01", "month")]
[InlineData("Week", null, "1999-W52", "week")]
[InlineData("Week", "{0:yyyy/'W1'}", "2000/W1", "week")]
[ReplaceCulture]
public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateTimeNotRfc3339(
string propertyName,
string editFormatString,
string expectedValue,
string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", propertyName },
{ "name", propertyName },
{ "value", expectedValue },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedType,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var model = new DateTime(
year: 2000,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5,
millisecond: 60,
kind: DateTimeKind.Utc);
var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
var tagHelper = GetTagHelper(
htmlGenerator,
model: model,
propertyName: propertyName,
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.CurrentCulture;
tagHelper.Format = editFormatString;
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedType, output.TagName);
}
// Html5DateRenderingMode.Rfc3339 can be disabled.
[Theory]
[InlineData("DateOnlyDate", null, "02/01/2000", "date")]
[InlineData("DateOnlyDate", "{0:d}", "02/01/2000", "date")]
[InlineData("DateOnly", null, "02/01/2000", "text")]
[ReplaceCulture]
public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateOnlyNotRfc3339(
string propertyName,
string editFormatString,
string expectedValue,
string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", propertyName },
{ "name", propertyName },
{ "value", expectedValue },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedType,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var model = new DateOnly(
year: 2000,
month: 1,
day: 2);
var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
var tagHelper = GetTagHelper(
htmlGenerator,
model: model,
propertyName: propertyName,
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.CurrentCulture;
tagHelper.Format = editFormatString;
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedType, output.TagName);
}
// Html5DateRenderingMode.Rfc3339 can be disabled.
[Theory]
[InlineData("Month", "month")]
[InlineData("Week", "week")]
[ReplaceCulture]
public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_OverridesDefaultFormat(
string propertyName,
string expectedType)
{
// Arrange
var expectedAttributes = new TagHelperAttributeList
{
{ "type", expectedType },
{ "id", propertyName },
{ "name", propertyName },
{ "value", "non-default format string" },
};
var context = new TagHelperContext(
tagName: "input",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedType,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = TagMode.SelfClosing,
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var model = new DateTime(
year: 2000,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5,
millisecond: 60,
kind: DateTimeKind.Utc);
var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
var tagHelper = GetTagHelper(
htmlGenerator,
model: model,
propertyName: propertyName,
metadataProvider: metadataProvider);
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.CurrentCulture;
tagHelper.Format = "non-default format string";
// Act
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedType, output.TagName);
}
private static InputTagHelper GetTagHelper(
IHtmlGenerator htmlGenerator,
object model,
string propertyName,
IModelMetadataProvider metadataProvider = null)
{
return GetTagHelper(
htmlGenerator,
container: new Model(),
containerType: typeof(Model),
model: model,
propertyName: propertyName,
expressionName: propertyName,
metadataProvider: metadataProvider);
}
private static InputTagHelper GetTagHelper(
IHtmlGenerator htmlGenerator,
object container,
Type containerType,
object model,
string propertyName,
string expressionName,
IModelMetadataProvider metadataProvider = null)
{
if (metadataProvider == null)
{
metadataProvider = new TestModelMetadataProvider();
}
var containerMetadata = metadataProvider.GetMetadataForType(containerType);
var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, container);
var propertyMetadata = metadataProvider.GetMetadataForProperty(containerType, propertyName);
var modelExplorer = containerExplorer.GetExplorerForExpression(propertyMetadata, model);
var modelExpression = new ModelExpression(expressionName, modelExplorer);
var viewContext = TestableHtmlGenerator.GetViewContext(container, htmlGenerator, metadataProvider);
var inputTagHelper = new InputTagHelper(htmlGenerator)
{
For = modelExpression,
ViewContext = viewContext,
};
return inputTagHelper;
}
public class NameAndId
{
public NameAndId(string name, string id)
{
Name = name;
Id = id;
}
public string Name { get; private set; }
public string Id { get; private set; }
}
private class Model
{
public string Text { get; set; }
public NestedModel NestedModel { get; set; }
public bool IsACar { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
public DateTime DateTime { get; set; }
[DataType(DataType.Date)]
public DateOnly DateOnlyDate { get; set; }
public DateOnly DateOnly { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
[DataType(DataType.Date)]
public DateTime? NullableDate { get; set; }
public DateTime? NullableDateTime { get; set; }
[DataType(DataType.Date)]
public DateOnly? NullableDateOnlyDate { get; set; }
public DateOnly? NullableDateOnly { get; set; }
public DateTimeOffset? NullableDateTimeOffset { get; set; }
[DataType("datetime-local")]
public DateTime DateTimeLocal { get; set; }
[DataType(DataType.Time)]
public DateTimeOffset Time { get; set; }
[DataType("month")]
public DateTimeOffset Month { get; set; }
[DataType("week")]
public DateTimeOffset Week { get; set; }
}
private class NestedModel
{
public string Text { get; set; }
}
}
|