EditContextDataAnnotationsExtensionsTest.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.ComponentModel.DataAnnotations;
  5. using Xunit;
  6. namespace Microsoft.AspNetCore.Components.Forms
  7. {
  8. public class EditContextDataAnnotationsExtensionsTest
  9. {
  10. [Fact]
  11. public void CannotUseNullEditContext()
  12. {
  13. var editContext = (EditContext)null;
  14. var ex = Assert.Throws<ArgumentNullException>(() => editContext.AddDataAnnotationsValidation());
  15. Assert.Equal("editContext", ex.ParamName);
  16. }
  17. [Fact]
  18. public void ReturnsEditContextForChaining()
  19. {
  20. var editContext = new EditContext(new object());
  21. var returnValue = editContext.AddDataAnnotationsValidation();
  22. Assert.Same(editContext, returnValue);
  23. }
  24. [Fact]
  25. public void GetsValidationMessagesFromDataAnnotations()
  26. {
  27. // Arrange
  28. var model = new TestModel { IntFrom1To100 = 101 };
  29. var editContext = new EditContext(model).AddDataAnnotationsValidation();
  30. // Act
  31. var isValid = editContext.Validate();
  32. // Assert
  33. Assert.False(isValid);
  34. Assert.Equal(new string[]
  35. {
  36. "RequiredString:required",
  37. "IntFrom1To100:range"
  38. },
  39. editContext.GetValidationMessages());
  40. Assert.Equal(new string[] { "RequiredString:required" },
  41. editContext.GetValidationMessages(editContext.Field(nameof(TestModel.RequiredString))));
  42. // This shows we're including non-[Required] properties in the validation results, i.e,
  43. // that we're correctly passing "validateAllProperties: true" to DataAnnotations
  44. Assert.Equal(new string[] { "IntFrom1To100:range" },
  45. editContext.GetValidationMessages(editContext.Field(nameof(TestModel.IntFrom1To100))));
  46. }
  47. [Fact]
  48. public void ClearsExistingValidationMessagesOnFurtherRuns()
  49. {
  50. // Arrange
  51. var model = new TestModel { IntFrom1To100 = 101 };
  52. var editContext = new EditContext(model).AddDataAnnotationsValidation();
  53. // Act/Assert 1: Initially invalid
  54. Assert.False(editContext.Validate());
  55. // Act/Assert 2: Can become valid
  56. model.RequiredString = "Hello";
  57. model.IntFrom1To100 = 100;
  58. Assert.True(editContext.Validate());
  59. }
  60. [Fact]
  61. public void NotifiesValidationStateChangedAfterObjectValidation()
  62. {
  63. // Arrange
  64. var model = new TestModel { IntFrom1To100 = 101 };
  65. var editContext = new EditContext(model).AddDataAnnotationsValidation();
  66. var onValidationStateChangedCount = 0;
  67. editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++;
  68. // Act/Assert 1: Notifies after invalid results
  69. Assert.False(editContext.Validate());
  70. Assert.Equal(1, onValidationStateChangedCount);
  71. // Act/Assert 2: Notifies after valid results
  72. model.RequiredString = "Hello";
  73. model.IntFrom1To100 = 100;
  74. Assert.True(editContext.Validate());
  75. Assert.Equal(2, onValidationStateChangedCount);
  76. // Act/Assert 3: Notifies even if results haven't changed. Later we might change the
  77. // logic to track the previous results and compare with the new ones, but that's just
  78. // an optimization. It's legal to notify regardless.
  79. Assert.True(editContext.Validate());
  80. Assert.Equal(3, onValidationStateChangedCount);
  81. }
  82. [Fact]
  83. public void PerformsPerPropertyValidationOnFieldChange()
  84. {
  85. // Arrange
  86. var model = new TestModel { IntFrom1To100 = 101 };
  87. var independentTopLevelModel = new object(); // To show we can validate things on any model, not just the top-level one
  88. var editContext = new EditContext(independentTopLevelModel).AddDataAnnotationsValidation();
  89. var onValidationStateChangedCount = 0;
  90. var requiredStringIdentifier = new FieldIdentifier(model, nameof(TestModel.RequiredString));
  91. var intFrom1To100Identifier = new FieldIdentifier(model, nameof(TestModel.IntFrom1To100));
  92. editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++;
  93. // Act/Assert 1: Notify about RequiredString
  94. // Only RequiredString gets validated, even though IntFrom1To100 also holds an invalid value
  95. editContext.NotifyFieldChanged(requiredStringIdentifier);
  96. Assert.Equal(1, onValidationStateChangedCount);
  97. Assert.Equal(new[] { "RequiredString:required" }, editContext.GetValidationMessages());
  98. // Act/Assert 2: Fix RequiredString, but only notify about IntFrom1To100
  99. // Only IntFrom1To100 gets validated; messages for RequiredString are left unchanged
  100. model.RequiredString = "This string is very cool and very legal";
  101. editContext.NotifyFieldChanged(intFrom1To100Identifier);
  102. Assert.Equal(2, onValidationStateChangedCount);
  103. Assert.Equal(new string[]
  104. {
  105. "RequiredString:required",
  106. "IntFrom1To100:range"
  107. },
  108. editContext.GetValidationMessages());
  109. // Act/Assert 3: Notify about RequiredString
  110. editContext.NotifyFieldChanged(requiredStringIdentifier);
  111. Assert.Equal(3, onValidationStateChangedCount);
  112. Assert.Equal(new[] { "IntFrom1To100:range" }, editContext.GetValidationMessages());
  113. }
  114. [Theory]
  115. [InlineData(nameof(TestModel.ThisWillNotBeValidatedBecauseItIsAField))]
  116. [InlineData(nameof(TestModel.ThisWillNotBeValidatedBecauseItIsInternal))]
  117. [InlineData("ThisWillNotBeValidatedBecauseItIsPrivate")]
  118. [InlineData("This does not correspond to anything")]
  119. [InlineData("")]
  120. public void IgnoresFieldChangesThatDoNotCorrespondToAValidatableProperty(string fieldName)
  121. {
  122. // Arrange
  123. var editContext = new EditContext(new TestModel()).AddDataAnnotationsValidation();
  124. var onValidationStateChangedCount = 0;
  125. editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++;
  126. // Act/Assert: Ignores field changes that don't correspond to a validatable property
  127. editContext.NotifyFieldChanged(editContext.Field(fieldName));
  128. Assert.Equal(0, onValidationStateChangedCount);
  129. // Act/Assert: For sanity, observe that we would have validated if it was a validatable property
  130. editContext.NotifyFieldChanged(editContext.Field(nameof(TestModel.RequiredString)));
  131. Assert.Equal(1, onValidationStateChangedCount);
  132. }
  133. class TestModel
  134. {
  135. [Required(ErrorMessage = "RequiredString:required")] public string RequiredString { get; set; }
  136. [Range(1, 100, ErrorMessage = "IntFrom1To100:range")] public int IntFrom1To100 { get; set; }
  137. #pragma warning disable 649
  138. [Required] public string ThisWillNotBeValidatedBecauseItIsAField;
  139. [Required] string ThisWillNotBeValidatedBecauseItIsPrivate { get; set; }
  140. [Required] internal string ThisWillNotBeValidatedBecauseItIsInternal { get; set; }
  141. #pragma warning restore 649
  142. }
  143. }
  144. }