-
Notifications
You must be signed in to change notification settings - Fork 493
Add New Enum Description Converter #3137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
db71c82
64a31a4
ee49e0f
2b2d254
17ecc76
eb5927b
745cfc3
6384d8a
7cf4bf7
94edbf7
1ff4d6a
120e642
121742e
d0afca0
f18eff0
295496b
150791c
28304c1
4f21f71
6e4e5ba
dd4d8f4
012182e
0c6f83f
5413fef
69b243c
3a012cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| using Xunit; | ||
|
|
||
| using CommunityToolkit.Maui.Converters; | ||
|
|
||
| using System.ComponentModel; | ||
| using System.ComponentModel.DataAnnotations; | ||
|
|
||
| namespace CommunityToolkit.Maui.UnitTests.Converters; | ||
|
|
||
| public class EnumDescriptionConverterTests : BaseOneWayConverterTest<EnumDescriptionConverter> | ||
| { | ||
| enum TestEnum | ||
| { | ||
| [Display(Name = "Display Name")] | ||
| WithDisplay, | ||
| [Description("Description Text")] | ||
| WithDescription, | ||
| NoAttribute | ||
| } | ||
|
|
||
| enum MultiAttributeEnum | ||
| { | ||
| [Display(Name = "Display Name")] | ||
| [Description("Description Text")] | ||
| Both, | ||
| [Display(Name = "")] | ||
| [Description("Description Text")] | ||
| EmptyDisplay, | ||
| [Display(Name = " ")] | ||
| [Description("Description Text")] | ||
| WhitespaceDisplay, | ||
| [Description("")] | ||
| EmptyDescription, | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_ThrowsArgumentNullException_WhenNull() | ||
| { | ||
| var converter = (ICommunityToolkitValueConverter)new EnumDescriptionConverter(); | ||
| Assert.Throws<ArgumentNullException>(() => converter.Convert(null, typeof(string), null, null)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToValueToString_WhenInvalidEnumValue() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| // Cast an int not defined in MultiAttributeEnum | ||
| var invalid = (MultiAttributeEnum)999; | ||
| var result = converter.ConvertFrom(invalid); | ||
| Assert.Equal("999", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_ReturnsDisplayName() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(TestEnum.WithDisplay); | ||
| Assert.Equal("Display Name", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_ReturnsDescriptionText() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(TestEnum.WithDescription); | ||
| Assert.Equal("Description Text", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_ReturnsEnumName_WhenNoAttribute() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(TestEnum.NoAttribute); | ||
| Assert.Equal("NoAttribute", result); | ||
| } | ||
|
|
||
|
|
||
| [Fact] | ||
| public void ConvertFrom_DisplayTakesPrecedence_WhenBothAttributes() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(MultiAttributeEnum.Both); | ||
| Assert.Equal("Display Name", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToDescription_WhenDisplayEmpty() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(MultiAttributeEnum.EmptyDisplay); | ||
| Assert.Equal("Description Text", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToDescription_WhenDisplayWhitespace() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(MultiAttributeEnum.WhitespaceDisplay); | ||
| Assert.Equal("Description Text", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToEnumName_WhenDescriptionEmpty() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(MultiAttributeEnum.EmptyDescription); | ||
| Assert.Equal("EmptyDescription", result); | ||
| } | ||
|
|
||
| #region DisplayAttribute Localization & Edge Cases | ||
|
|
||
| public class TestResources | ||
| { | ||
| public static string LocalizedDisplay => "Localized Display"; | ||
| public static int NonStringResource => 42; | ||
| } | ||
|
|
||
| enum LocalizedEnum | ||
| { | ||
| [Display(Name = "LocalizedDisplay", ResourceType = typeof(TestResources))] | ||
| Localized, | ||
| [Display(Name = "MissingResource", ResourceType = typeof(TestResources))] | ||
| MissingResource, | ||
| [Display(Name = null)] | ||
| NullName, | ||
| [Display(Name = " ")] | ||
| WhitespaceName, | ||
| [Display(Name = "NonStringResource", ResourceType = typeof(TestResources))] | ||
| NonStringResource, | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_ReturnsLocalizedDisplay_WhenResourceTypeSet() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(LocalizedEnum.Localized); | ||
| Assert.Equal("Localized Display", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToName_WhenResourceMissing() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(LocalizedEnum.MissingResource); | ||
| Assert.Equal("MissingResource", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToEnumName_WhenDisplayNameNull() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(LocalizedEnum.NullName); | ||
| Assert.Equal("NullName", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToEnumName_WhenDisplayNameWhitespace() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(LocalizedEnum.WhitespaceName); | ||
| Assert.Equal("WhitespaceName", result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToEnumName_WhenGetNameThrows() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(LocalizedEnum.NonStringResource); | ||
| Assert.Equal("NonStringResource", result); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region DescriptionAttribute Edge Cases | ||
|
|
||
| enum DescriptionEdgeEnum | ||
| { | ||
| [Description("")] | ||
| EmptyDescription, | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ConvertFrom_FallbackToEnumName_WhenDescriptionEmptyEdge() | ||
| { | ||
| var converter = new EnumDescriptionConverter(); | ||
| var result = converter.ConvertFrom(DescriptionEdgeEnum.EmptyDescription); | ||
| Assert.Equal("EmptyDescription", result); | ||
| } | ||
|
|
||
| #endregion | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||
| using System.ComponentModel; | ||||||
| using System.ComponentModel.DataAnnotations; | ||||||
| using System.Globalization; | ||||||
| using System.Reflection; | ||||||
|
|
||||||
| namespace CommunityToolkit.Maui.Converters; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Converts an <see cref="Enum"/> value to its display string using <see cref="DisplayAttribute"/> or <see cref="DescriptionAttribute"/>. | ||||||
| /// </summary> | ||||||
| [AcceptEmptyServiceProvider] | ||||||
| public partial class EnumDescriptionConverter : BaseConverterOneWay<Enum, string> | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
It would make sense for this converter to return a nullable string,
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I originally had a null return, but the copilots reviewer said we should return the key if the user was localizing. What do you think? |
||||||
| { | ||||||
| /// <inheritdoc/> | ||||||
| public override string DefaultConvertReturnValue { get; set; } = string.Empty; | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
It would make sense for this converter to return a nullable string and return |
||||||
|
|
||||||
| /// <summary> | ||||||
| /// Converts an <see cref="Enum"/> value to its display string. | ||||||
| /// </summary> | ||||||
| /// <param name="value">The enum value to convert.</param> | ||||||
| /// <param name="culture">The culture to use for the conversion (not used).</param> | ||||||
| /// <returns> | ||||||
| /// The value of the <see cref="DisplayAttribute.Name"/> if defined; | ||||||
| /// otherwise the value of the <see cref="DescriptionAttribute.Description"/> if defined; | ||||||
| /// otherwise the enum name as a string. | ||||||
| /// </returns> | ||||||
| public override string ConvertFrom(Enum value, CultureInfo? culture = null) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
It would make sense for this converter to return a nullable string and return |
||||||
| { | ||||||
| ArgumentNullException.ThrowIfNull(value); | ||||||
| var fieldInfo = value.GetType().GetField(value.ToString()); | ||||||
|
|
||||||
BillyMartin1964 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| // Check for DisplayAttribute first (common in data annotations) | ||||||
| var displayAttr = fieldInfo?.GetCustomAttribute<DisplayAttribute>(); | ||||||
| if (displayAttr is not null) | ||||||
| { | ||||||
| string? displayName = null; | ||||||
| try | ||||||
| { | ||||||
| displayName = displayAttr.GetName(); | ||||||
| } | ||||||
| catch | ||||||
| { | ||||||
| // If GetName throws, fallback to Name | ||||||
| displayName = null; | ||||||
| } | ||||||
| if (!string.IsNullOrWhiteSpace(displayName)) | ||||||
| { | ||||||
| return displayName; | ||||||
| } | ||||||
| if (!string.IsNullOrWhiteSpace(displayAttr.Name)) | ||||||
| { | ||||||
| return displayAttr.Name; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Fallback to DescriptionAttribute | ||||||
| var descriptionAttr = fieldInfo?.GetCustomAttribute<DescriptionAttribute>(); | ||||||
| if (descriptionAttr is not null && !string.IsNullOrWhiteSpace(descriptionAttr.Description)) | ||||||
| { | ||||||
| return descriptionAttr.Description; | ||||||
| } | ||||||
|
|
||||||
| return value.ToString(); // Fallback to enum name if no attribute found | ||||||
|
||||||
| return value.ToString(); // Fallback to enum name if no attribute found | |
| return value.ToString(); // Fallback to enum name if no attribute found |
It would make sense for this converter to return a nullable string and return null when an enum does not contain a Description attribute.
Uh oh!
There was an error while loading. Please reload this page.