From 390eb4c2620bbbff681b926162b775331155feb1 Mon Sep 17 00:00:00 2001 From: zHaytam Date: Thu, 16 Jul 2020 19:03:42 +0100 Subject: [PATCH 1/7] Add DisplayName --- src/Components/Web/src/Forms/InputBase.cs | 5 +++++ src/Components/Web/src/Forms/InputDate.cs | 2 +- src/Components/Web/src/Forms/InputExtensions.cs | 2 +- src/Components/Web/src/Forms/InputNumber.cs | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Components/Web/src/Forms/InputBase.cs b/src/Components/Web/src/Forms/InputBase.cs index 5a8fceecf822..62396ecabdcd 100644 --- a/src/Components/Web/src/Forms/InputBase.cs +++ b/src/Components/Web/src/Forms/InputBase.cs @@ -50,6 +50,11 @@ public abstract class InputBase : ComponentBase, IDisposable /// [Parameter] public Expression>? ValueExpression { get; set; } + /// + /// Gets or sets the display name for this field. + /// + [Parameter] public string? DisplayName { get; set; } + /// /// Gets the associated . /// diff --git a/src/Components/Web/src/Forms/InputDate.cs b/src/Components/Web/src/Forms/InputDate.cs index e7132988b900..4372646c7b15 100644 --- a/src/Components/Web/src/Forms/InputDate.cs +++ b/src/Components/Web/src/Forms/InputDate.cs @@ -75,7 +75,7 @@ protected override bool TryParseValueFromString(string? value, [MaybeNull] out T } else { - validationErrorMessage = string.Format(ParsingErrorMessage, FieldIdentifier.FieldName); + validationErrorMessage = string.Format(ParsingErrorMessage, DisplayName ?? FieldIdentifier.FieldName); return false; } } diff --git a/src/Components/Web/src/Forms/InputExtensions.cs b/src/Components/Web/src/Forms/InputExtensions.cs index a1ace921410b..748af5c78e88 100644 --- a/src/Components/Web/src/Forms/InputExtensions.cs +++ b/src/Components/Web/src/Forms/InputExtensions.cs @@ -22,7 +22,7 @@ public static bool TryParseSelectableValueFromString(this InputBase Date: Thu, 16 Jul 2020 19:15:44 +0100 Subject: [PATCH 2/7] Add InputNumber and InputDate tests --- .../Web/test/Forms/InputDateTest.cs | 53 +++++++++++++++++++ .../Web/test/Forms/InputNumberTest.cs | 52 ++++++++++++++++++ .../Web/test/Forms/InputRenderer.cs | 25 +++++++++ .../Web/test/Forms/TestInputHostComponent.cs | 38 +++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 src/Components/Web/test/Forms/InputDateTest.cs create mode 100644 src/Components/Web/test/Forms/InputNumberTest.cs create mode 100644 src/Components/Web/test/Forms/InputRenderer.cs create mode 100644 src/Components/Web/test/Forms/TestInputHostComponent.cs diff --git a/src/Components/Web/test/Forms/InputDateTest.cs b/src/Components/Web/test/Forms/InputDateTest.cs new file mode 100644 index 000000000000..622e7d496b96 --- /dev/null +++ b/src/Components/Web/test/Forms/InputDateTest.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Components.Forms +{ + public class InputDateTest + { + [Fact] + public async Task ValidationErrorUsesDisplayAttributeName() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.DateProperty, + AdditionalAttributes = new Dictionary + { + { "DisplayName", "Date property" } + } + }; + var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); + + // Act + await inputComponent.SetCurrentValueAsStringAsync("invalidDate"); + + // Assert + var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier); + Assert.NotEmpty(validationMessages); + Assert.Contains("The Date property field must be a date.", validationMessages); + } + + private class TestModel + { + public DateTime DateProperty { get; set; } + } + + private class TestInputDateComponent : InputDate + { + public async Task SetCurrentValueAsStringAsync(string value) + { + // This is equivalent to the subclass writing to CurrentValueAsString + // (e.g., from @bind), except to simplify the test code there's an InvokeAsync + // here. In production code it wouldn't normally be required because @bind + // calls run on the sync context anyway. + await InvokeAsync(() => { base.CurrentValueAsString = value; }); + } + } + } +} diff --git a/src/Components/Web/test/Forms/InputNumberTest.cs b/src/Components/Web/test/Forms/InputNumberTest.cs new file mode 100644 index 000000000000..5580587c7096 --- /dev/null +++ b/src/Components/Web/test/Forms/InputNumberTest.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Components.Forms +{ + public class InputNumberTest + { + [Fact] + public async Task ValidationErrorUsesDisplayAttributeName() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.SomeNumber, + AdditionalAttributes = new Dictionary + { + { "DisplayName", "Some number" } + } + }; + var fieldIdentifier = FieldIdentifier.Create(() => model.SomeNumber); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); + + // Act + await inputComponent.SetCurrentValueAsStringAsync("notANumber"); + + // Assert + var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier); + Assert.NotEmpty(validationMessages); + Assert.Contains("The Some number field must be a number.", validationMessages); + } + + private class TestModel + { + public int SomeNumber { get; set; } + } + + private class TestInputNumberComponent : InputNumber + { + public async Task SetCurrentValueAsStringAsync(string value) + { + // This is equivalent to the subclass writing to CurrentValueAsString + // (e.g., from @bind), except to simplify the test code there's an InvokeAsync + // here. In production code it wouldn't normally be required because @bind + // calls run on the sync context anyway. + await InvokeAsync(() => { base.CurrentValueAsString = value; }); + } + } + } +} diff --git a/src/Components/Web/test/Forms/InputRenderer.cs b/src/Components/Web/test/Forms/InputRenderer.cs new file mode 100644 index 000000000000..9738311d2c08 --- /dev/null +++ b/src/Components/Web/test/Forms/InputRenderer.cs @@ -0,0 +1,25 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.AspNetCore.Components.Test.Helpers; + +namespace Microsoft.AspNetCore.Components.Forms +{ + internal static class InputRenderer + { + public static async Task RenderAndGetComponent(TestInputHostComponent hostComponent) where TComponent : InputBase + { + var testRenderer = new TestRenderer(); + var componentId = testRenderer.AssignRootComponentId(hostComponent); + await testRenderer.RenderRootComponentAsync(componentId); + return FindComponent(testRenderer.Batches.Single()); + } + + private static TComponent FindComponent(CapturedBatch batch) + => batch.ReferenceFrames + .Where(f => f.FrameType == RenderTreeFrameType.Component) + .Select(f => f.Component) + .OfType() + .Single(); + } +} diff --git a/src/Components/Web/test/Forms/TestInputHostComponent.cs b/src/Components/Web/test/Forms/TestInputHostComponent.cs new file mode 100644 index 000000000000..67a46d344700 --- /dev/null +++ b/src/Components/Web/test/Forms/TestInputHostComponent.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Test.Helpers; + +namespace Microsoft.AspNetCore.Components.Forms +{ + internal class TestInputHostComponent : AutoRenderComponent where TComponent : InputBase + { + public Dictionary AdditionalAttributes { get; set; } + + public EditContext EditContext { get; set; } + + public TValue Value { get; set; } + + public Action ValueChanged { get; set; } + + public Expression> ValueExpression { get; set; } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Value", EditContext); + builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder => + { + childBuilder.OpenComponent(0); + childBuilder.AddAttribute(0, "Value", Value); + childBuilder.AddAttribute(1, "ValueChanged", + EventCallback.Factory.Create(this, ValueChanged)); + childBuilder.AddAttribute(2, "ValueExpression", ValueExpression); + childBuilder.AddMultipleAttributes(3, AdditionalAttributes); + childBuilder.CloseComponent(); + })); + builder.CloseComponent(); + } + } +} From 9b10b96f6e9b2cf5acf230c5108b001c7b3bcfaf Mon Sep 17 00:00:00 2001 From: zHaytam Date: Thu, 16 Jul 2020 19:29:18 +0100 Subject: [PATCH 3/7] Add InputSelect validation test --- .../Web/test/Forms/InputSelectTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Components/Web/test/Forms/InputSelectTest.cs b/src/Components/Web/test/Forms/InputSelectTest.cs index 65c3351e5eea..0fe2370a40e9 100644 --- a/src/Components/Web/test/Forms/InputSelectTest.cs +++ b/src/Components/Web/test/Forms/InputSelectTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; @@ -172,6 +173,32 @@ public async Task ParsesCurrentValueWhenUsingNullableInt() Assert.Equal(42, inputSelectComponent.CurrentValue); } + [Fact] + public async Task ValidationErrorUsesDisplayAttributeName() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputHostComponent> + { + EditContext = new EditContext(model), + ValueExpression = () => model.NotNullableInt, + AdditionalAttributes = new Dictionary + { + { "DisplayName", "Some number" } + } + }; + var fieldIdentifier = FieldIdentifier.Create(() => model.NotNullableInt); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); + + // Act + await inputSelectComponent.SetCurrentValueAsStringAsync("invalidNumber"); + + // Assert + var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier); + Assert.NotEmpty(validationMessages); + Assert.Contains("The Some number field is not valid.", validationMessages); + } + private static TestInputSelect FindInputSelectComponent(CapturedBatch batch) => batch.ReferenceFrames .Where(f => f.FrameType == RenderTreeFrameType.Component) @@ -218,6 +245,14 @@ class TestInputSelect : InputSelect get => base.CurrentValueAsString; set => base.CurrentValueAsString = value; } + public async Task SetCurrentValueAsStringAsync(string value) + { + // This is equivalent to the subclass writing to CurrentValueAsString + // (e.g., from @bind), except to simplify the test code there's an InvokeAsync + // here. In production code it wouldn't normally be required because @bind + // calls run on the sync context anyway. + await InvokeAsync(() => { base.CurrentValueAsString = value; }); + } } class TestInputSelectHostComponent : AutoRenderComponent From 0ed2a5438885959221e57864bb1b09831bd69fa5 Mon Sep 17 00:00:00 2001 From: zHaytam Date: Thu, 16 Jul 2020 19:36:54 +0100 Subject: [PATCH 4/7] Refactor code to use InputRenderer and shared TestInputHostComponent --- .../Web/test/Forms/InputBaseTest.cs | 78 ++++--------------- .../Web/test/Forms/InputSelectTest.cs | 72 ++++------------- 2 files changed, 31 insertions(+), 119 deletions(-) diff --git a/src/Components/Web/test/Forms/InputBaseTest.cs b/src/Components/Web/test/Forms/InputBaseTest.cs index 26464b83869f..6a8da5ca7666 100644 --- a/src/Components/Web/test/Forms/InputBaseTest.cs +++ b/src/Components/Web/test/Forms/InputBaseTest.cs @@ -4,10 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; using Xunit; @@ -35,7 +32,7 @@ public async Task ThrowsIfEditContextChanges() // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.StringProperty }; - await RenderAndGetTestInputComponentAsync(rootComponent); + await InputRenderer.RenderAndGetComponent(rootComponent); // Act/Assert rootComponent.EditContext = new EditContext(model); @@ -51,7 +48,7 @@ public async Task ThrowsIfNoValueExpressionIsSupplied() var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model) }; // Act/Assert - var ex = await Assert.ThrowsAsync(() => RenderAndGetTestInputComponentAsync(rootComponent)); + var ex = await Assert.ThrowsAsync(() => InputRenderer.RenderAndGetComponent(rootComponent)); Assert.Contains($"{typeof(TestInputComponent)} requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'.", ex.Message); } @@ -68,7 +65,7 @@ public async Task GetsCurrentValueFromValueParameter() }; // Act - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Assert Assert.Equal("some value", inputComponent.CurrentValue); @@ -87,7 +84,7 @@ public async Task ExposesEditContextToSubclass() }; // Act - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Assert Assert.Same(rootComponent.EditContext, inputComponent.EditContext); @@ -106,7 +103,7 @@ public async Task ExposesFieldIdentifierToSubclass() }; // Act - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Assert Assert.Equal(FieldIdentifier.Create(() => model.StringProperty), inputComponent.FieldIdentifier); @@ -123,7 +120,7 @@ public async Task CanReadBackChangesToCurrentValue() Value = "initial value", ValueExpression = () => model.StringProperty }; - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); Assert.Equal("initial value", inputComponent.CurrentValue); // Act @@ -146,7 +143,7 @@ public async Task WritingToCurrentValueInvokesValueChangedIfDifferent() ValueChanged = val => valueChangedCallLog.Add(val), ValueExpression = () => model.StringProperty }; - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); Assert.Empty(valueChangedCallLog); // Act @@ -169,7 +166,7 @@ public async Task WritingToCurrentValueDoesNotInvokeValueChangedIfUnchanged() ValueChanged = val => valueChangedCallLog.Add(val), ValueExpression = () => model.StringProperty }; - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); Assert.Empty(valueChangedCallLog); // Act @@ -190,7 +187,7 @@ public async Task WritingToCurrentValueNotifiesEditContext() Value = "initial value", ValueExpression = () => model.StringProperty }; - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); Assert.False(rootComponent.EditContext.IsModified(() => model.StringProperty)); // Act @@ -213,7 +210,7 @@ public async Task SuppliesFieldClassCorrespondingToFieldState() var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); // Act/Assert: Initially, it's valid and unmodified - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); Assert.Equal("valid", inputComponent.CssClass); // no Class was specified // Act/Assert: Modify the field @@ -251,7 +248,7 @@ public async Task CssClassCombinesClassWithFieldClass() var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); // Act/Assert - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); Assert.Equal("my-class other-class valid", inputComponent.CssClass); // Act/Assert: Retains custom class when changing field class @@ -270,7 +267,7 @@ public async Task SuppliesCurrentValueAsStringWithFormatting() Value = new DateTime(1915, 3, 2), ValueExpression = () => model.DateProperty }; - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act/Assert Assert.Equal("1915/03/02", inputComponent.CurrentValueAsString); @@ -289,7 +286,7 @@ public async Task ParsesCurrentValueAsStringWhenChanged_Valid() ValueExpression = () => model.DateProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty); - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); var numValidationStateChanges = 0; rootComponent.EditContext.OnValidationStateChanged += (sender, eventArgs) => { numValidationStateChanges++; }; @@ -319,7 +316,7 @@ public async Task ParsesCurrentValueAsStringWhenChanged_Invalid() ValueExpression = () => model.DateProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty); - var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); var numValidationStateChanges = 0; rootComponent.EditContext.OnValidationStateChanged += (sender, eventArgs) => { numValidationStateChanges++; }; @@ -470,21 +467,6 @@ public async Task UserSpecifiedAriaValueIsNotChangedIfInvalid() Assert.Equal("userSpecifiedValue", component.AdditionalAttributes["aria-invalid"]); } - private static TComponent FindComponent(CapturedBatch batch) - => batch.ReferenceFrames - .Where(f => f.FrameType == RenderTreeFrameType.Component) - .Select(f => f.Component) - .OfType() - .Single(); - - private static async Task RenderAndGetTestInputComponentAsync(TestInputHostComponent hostComponent) where TComponent : TestInputComponent - { - var testRenderer = new TestRenderer(); - var componentId = testRenderer.AssignRootComponentId(hostComponent); - await testRenderer.RenderRootComponentAsync(componentId); - return FindComponent(testRenderer.Batches.Single()); - } - class TestModel { public string StringProperty { get; set; } @@ -530,7 +512,7 @@ public async Task SetCurrentValueAsStringAsync(string value) } } - class TestDateInputComponent : TestInputComponent + private class TestDateInputComponent : TestInputComponent { protected override string FormatValueAsString(DateTime value) => value.ToString("yyyy/MM/dd"); @@ -549,35 +531,5 @@ protected override bool TryParseValueFromString(string value, out DateTime resul } } } - - class TestInputHostComponent : AutoRenderComponent where TComponent : TestInputComponent - { - public Dictionary AdditionalAttributes { get; set; } - - public EditContext EditContext { get; set; } - - public TValue Value { get; set; } - - public Action ValueChanged { get; set; } - - public Expression> ValueExpression { get; set; } - - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.OpenComponent>(0); - builder.AddAttribute(1, "Value", EditContext); - builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder => - { - childBuilder.OpenComponent(0); - childBuilder.AddAttribute(0, "Value", Value); - childBuilder.AddAttribute(1, "ValueChanged", - EventCallback.Factory.Create(this, ValueChanged)); - childBuilder.AddAttribute(2, "ValueExpression", ValueExpression); - childBuilder.AddMultipleAttributes(3, AdditionalAttributes); - childBuilder.CloseComponent(); - })); - builder.CloseComponent(); - } - } } } diff --git a/src/Components/Web/test/Forms/InputSelectTest.cs b/src/Components/Web/test/Forms/InputSelectTest.cs index 0fe2370a40e9..8945867fd0da 100644 --- a/src/Components/Web/test/Forms/InputSelectTest.cs +++ b/src/Components/Web/test/Forms/InputSelectTest.cs @@ -3,12 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Components.Test.Helpers; using Xunit; namespace Microsoft.AspNetCore.Components.Forms @@ -20,12 +15,12 @@ public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithNotEmptyValue() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NotNullableEnum }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act inputSelectComponent.CurrentValueAsString = "Two"; @@ -39,12 +34,12 @@ public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithEmptyValue() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NotNullableEnum }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act inputSelectComponent.CurrentValueAsString = ""; @@ -58,12 +53,12 @@ public async Task ParsesCurrentValueWhenUsingNullableEnumWithNotEmptyValue() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NullableEnum }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act inputSelectComponent.CurrentValueAsString = "Two"; @@ -77,12 +72,12 @@ public async Task ParsesCurrentValueWhenUsingNullableEnumWithEmptyValue() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NullableEnum }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act inputSelectComponent.CurrentValueAsString = ""; @@ -97,12 +92,12 @@ public async Task ParsesCurrentValueWhenUsingNotNullableGuid() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NotNullableGuid }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act var guid = Guid.NewGuid(); @@ -118,12 +113,12 @@ public async Task ParsesCurrentValueWhenUsingNullableGuid() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NullableGuid }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act var guid = Guid.NewGuid(); @@ -139,12 +134,12 @@ public async Task ParsesCurrentValueWhenUsingNotNullableInt() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NotNullableInt }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act inputSelectComponent.CurrentValueAsString = "42"; @@ -159,12 +154,12 @@ public async Task ParsesCurrentValueWhenUsingNullableInt() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputSelectHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), ValueExpression = () => model.NullableInt }; - var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act inputSelectComponent.CurrentValueAsString = "42"; @@ -199,21 +194,6 @@ public async Task ValidationErrorUsesDisplayAttributeName() Assert.Contains("The Some number field is not valid.", validationMessages); } - private static TestInputSelect FindInputSelectComponent(CapturedBatch batch) - => batch.ReferenceFrames - .Where(f => f.FrameType == RenderTreeFrameType.Component) - .Select(f => f.Component) - .OfType>() - .Single(); - - private static async Task> RenderAndGetTestInputComponentAsync(TestInputSelectHostComponent hostComponent) - { - var testRenderer = new TestRenderer(); - var componentId = testRenderer.AssignRootComponentId(hostComponent); - await testRenderer.RenderRootComponentAsync(componentId); - return FindInputSelectComponent(testRenderer.Batches.Single()); - } - enum TestEnum { One, @@ -254,25 +234,5 @@ public async Task SetCurrentValueAsStringAsync(string value) await InvokeAsync(() => { base.CurrentValueAsString = value; }); } } - - class TestInputSelectHostComponent : AutoRenderComponent - { - public EditContext EditContext { get; set; } - - public Expression> ValueExpression { get; set; } - - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.OpenComponent>(0); - builder.AddAttribute(1, "Value", EditContext); - builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder => - { - childBuilder.OpenComponent>(0); - childBuilder.AddAttribute(0, "ValueExpression", ValueExpression); - childBuilder.CloseComponent(); - })); - builder.CloseComponent(); - } - } } } From d9667a9b2e5f9fb0991e9efef49632a474644b72 Mon Sep 17 00:00:00 2001 From: Haytam Zanid <34218324+zHaytam@users.noreply.github.com> Date: Thu, 16 Jul 2020 19:59:54 +0100 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Pranav K --- src/Components/Web/test/Forms/InputDateTest.cs | 3 +++ src/Components/Web/test/Forms/InputNumberTest.cs | 3 +++ src/Components/Web/test/Forms/InputRenderer.cs | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Components/Web/test/Forms/InputDateTest.cs b/src/Components/Web/test/Forms/InputDateTest.cs index 622e7d496b96..9c6f87b8321b 100644 --- a/src/Components/Web/test/Forms/InputDateTest.cs +++ b/src/Components/Web/test/Forms/InputDateTest.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/Components/Web/test/Forms/InputNumberTest.cs b/src/Components/Web/test/Forms/InputNumberTest.cs index 5580587c7096..6916f0e06e03 100644 --- a/src/Components/Web/test/Forms/InputNumberTest.cs +++ b/src/Components/Web/test/Forms/InputNumberTest.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System.Collections.Generic; using System.Threading.Tasks; using Xunit; diff --git a/src/Components/Web/test/Forms/InputRenderer.cs b/src/Components/Web/test/Forms/InputRenderer.cs index 9738311d2c08..3c8398191597 100644 --- a/src/Components/Web/test/Forms/InputRenderer.cs +++ b/src/Components/Web/test/Forms/InputRenderer.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.RenderTree; @@ -7,7 +10,8 @@ namespace Microsoft.AspNetCore.Components.Forms { internal static class InputRenderer { - public static async Task RenderAndGetComponent(TestInputHostComponent hostComponent) where TComponent : InputBase + public static async Task RenderAndGetComponent(TestInputHostComponent hostComponent) + where TComponent : InputBase { var testRenderer = new TestRenderer(); var componentId = testRenderer.AssignRootComponentId(hostComponent); From 614b57af9d9e1157e54bd206f7624ed159558ce4 Mon Sep 17 00:00:00 2001 From: zHaytam Date: Thu, 16 Jul 2020 20:03:38 +0100 Subject: [PATCH 6/7] Add license and DisplayName usage --- src/Components/Web/src/Forms/InputBase.cs | 1 + src/Components/Web/test/Forms/TestInputHostComponent.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/Components/Web/src/Forms/InputBase.cs b/src/Components/Web/src/Forms/InputBase.cs index 62396ecabdcd..08fd03c92a9d 100644 --- a/src/Components/Web/src/Forms/InputBase.cs +++ b/src/Components/Web/src/Forms/InputBase.cs @@ -52,6 +52,7 @@ public abstract class InputBase : ComponentBase, IDisposable /// /// Gets or sets the display name for this field. + /// This value is used when generating error messages when the input value fails to parse correctly. /// [Parameter] public string? DisplayName { get; set; } diff --git a/src/Components/Web/test/Forms/TestInputHostComponent.cs b/src/Components/Web/test/Forms/TestInputHostComponent.cs index 67a46d344700..4eb194a7180c 100644 --- a/src/Components/Web/test/Forms/TestInputHostComponent.cs +++ b/src/Components/Web/test/Forms/TestInputHostComponent.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using System.Linq.Expressions; From 0f3bb43e374989c54c9555675175b1bc882123fa Mon Sep 17 00:00:00 2001 From: zHaytam Date: Thu, 16 Jul 2020 20:16:48 +0100 Subject: [PATCH 7/7] Regenerate reference source --- .../Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs index 795dbe6ea3ed..14ff4f736781 100644 --- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs +++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs @@ -60,6 +60,8 @@ protected InputBase() { } [System.Diagnostics.CodeAnalysis.AllowNullAttribute] protected TValue CurrentValue { get { throw null; } set { } } protected string? CurrentValueAsString { get { throw null; } set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public string? DisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected internal Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute]