Merge branch 'feature/async-delegates' into 'dev'

Resolve "Support async for all delegates"

Closes #19

See merge request leon.hoppe/hopframe!22
This commit was merged in pull request #60.
This commit is contained in:
2025-01-27 16:57:43 +00:00
8 changed files with 97 additions and 57 deletions

View File

@@ -11,16 +11,14 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added missing files"> <list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added a simple web api abstraction method">
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/App.razor" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing.Api/HopFrame.Testing.Api.csproj" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfig.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing.Api/Program.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfig.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing.Api/Properties/launchSettings.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/ITableManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/ITableManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing.Api/appsettings.Development.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/TableManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/TableManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing.Api/appsettings.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/HopFrame.sln" beforeDir="false" afterPath="$PROJECT_DIR$/HopFrame.sln" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/ServiceCollectionExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/ServiceCollectionExtensions.cs" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -40,7 +38,7 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY"> <option name="RECENT_BRANCH_BY_REPOSITORY">
<map> <map>
<entry key="$PROJECT_DIR$" value="fix/missing-styles" /> <entry key="$PROJECT_DIR$" value="feature/api-abstraction" />
</map> </map>
</option> </option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@@ -69,11 +67,17 @@
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/adcd2c45092dd8e4fc412325c8adb75d6e7d8b3e90a9523f167583fb9c60/ServiceCollectionExtensions.cs" root0="SKIP_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/adcd2c45092dd8e4fc412325c8adb75d6e7d8b3e90a9523f167583fb9c60/ServiceCollectionExtensions.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/b3ccb66df3646cb51df73ad51716136ebd2eefb4edb1308dd52a7e999582d59e/IBindableColumn.cs" root0="SKIP_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/b3ccb66df3646cb51df73ad51716136ebd2eefb4edb1308dd52a7e999582d59e/IBindableColumn.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bfff78ecaa39c818519fc918bb2d4bbdca6ad93d7170f5cf325f67ccd0b97d43/BooleanAsserts.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bfff78ecaa39c818519fc918bb2d4bbdca6ad93d7170f5cf325f67ccd0b97d43/BooleanAsserts.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d39923abb31e6a6e7a9e8173e217da584c54925ce63e568126a2b89b9ab/DefaultRazorComponentsServiceOptionsConfiguration.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d858ddb35a8e36df5573b7612542f9ad50f426b8ab43818587d1ac65fab14829/DatabaseGeneratedAttribute.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d858ddb35a8e36df5573b7612542f9ad50f426b8ab43818587d1ac65fab14829/DatabaseGeneratedAttribute.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ece8533187fe96ce67b3ef1c9cc3502ef8da5510aadb132a9b21c5605d7c2119/PropertyColumn.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ece8533187fe96ce67b3ef1c9cc3502ef8da5510aadb132a9b21c5605d7c2119/PropertyColumn.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ee4d234452e240d83e3de396c2e85cbf9ac9fb9add618b955eea196c81aaf8/IDialogContentComponent.cs" root0="SKIP_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ee4d234452e240d83e3de396c2e85cbf9ac9fb9add618b955eea196c81aaf8/IDialogContentComponent.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/fc2027f7e776fc105cddb56b1a25eeb3895b3ae6f3aac854d786e63bd01f75e2/CallSiteFactory.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ff37d54b3bf4d2756237fb789635831532603376e940f63d634b869d26d74c/Regular16.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ff37d54b3bf4d2756237fb789635831532603376e940f63d634b869d26d74c/Regular16.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfig.cs" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfig.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/src/HopFrame.Core/Config/PropertyConfig.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/src/HopFrame.Core/Config/PropertyConfig.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/src/HopFrame.Core/Config/PropertyConfig.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor" root0="SKIP_HIGHLIGHTING" root1="FORCE_HIGHLIGHTING" root2="FORCE_HIGHLIGHTING" />
</component> </component>
<component name="KubernetesApiPersistence">{}</component> <component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{ <component name="KubernetesApiProvider">{
@@ -102,7 +106,7 @@
"RunOnceActivity.git.unshallow": "true", "RunOnceActivity.git.unshallow": "true",
"b5f11219-dfc4-47a1-b02c-90ab603034fb.executor": "Debug", "b5f11219-dfc4-47a1-b02c-90ab603034fb.executor": "Debug",
"dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug", "dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug",
"git-widget-placeholder": "!21 on feature/api-abstraction", "git-widget-placeholder": "!22 on feature/async-delegates",
"list.type.of.created.stylesheet": "CSS", "list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true", "node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true", "node.js.detected.package.tslint": "true",
@@ -207,7 +211,7 @@
<workItem from="1737293153907" duration="8953000" /> <workItem from="1737293153907" duration="8953000" />
<workItem from="1737390240714" duration="60000" /> <workItem from="1737390240714" duration="60000" />
<workItem from="1737390360987" duration="601000" /> <workItem from="1737390360987" duration="601000" />
<workItem from="1737993570961" duration="1590000" /> <workItem from="1737993570961" duration="3116000" />
</task> </task>
<task id="LOCAL-00001" summary="Added basic configuration"> <task id="LOCAL-00001" summary="Added basic configuration">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -393,7 +397,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1737994074137</updated> <updated>1737994074137</updated>
</task> </task>
<option name="localTasksCounter" value="24" /> <task id="LOCAL-00024" summary="Added a simple web api abstraction method">
<option name="closed" value="true" />
<created>1737995782424</created>
<option name="number" value="00024" />
<option name="presentableId" value="LOCAL-00024" />
<option name="project" value="LOCAL" />
<updated>1737995782424</updated>
</task>
<option name="localTasksCounter" value="25" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -467,6 +479,7 @@
<MESSAGE value="prepared project for release" /> <MESSAGE value="prepared project for release" />
<MESSAGE value="Included readme file in projects" /> <MESSAGE value="Included readme file in projects" />
<MESSAGE value="Added missing files" /> <MESSAGE value="Added missing files" />
<option name="LAST_COMMIT_MESSAGE" value="Added missing files" /> <MESSAGE value="Added a simple web api abstraction method" />
<option name="LAST_COMMIT_MESSAGE" value="Added a simple web api abstraction method" />
</component> </component>
</project> </project>

View File

@@ -11,9 +11,9 @@ public class PropertyConfig(PropertyInfo info, TableConfig table, int nthPropert
public bool Sortable { get; set; } = true; public bool Sortable { get; set; } = true;
public bool Searchable { get; set; } = true; public bool Searchable { get; set; } = true;
public PropertyInfo? DisplayedProperty { get; set; } public PropertyInfo? DisplayedProperty { get; set; }
public Func<object, IServiceProvider, string>? Formatter { get; set; } public Func<object, IServiceProvider, Task<string>>? Formatter { get; set; }
public Func<object, IServiceProvider, string>? EnumerableFormatter { get; set; } public Func<object, IServiceProvider, Task<string>>? EnumerableFormatter { get; set; }
public Func<string, IServiceProvider, object>? Parser { get; set; } public Func<string, IServiceProvider, Task<object>>? Parser { get; set; }
public Func<object?, IServiceProvider, Task<IEnumerable<string>>>? Validator { get; set; } public Func<object?, IServiceProvider, Task<IEnumerable<string>>>? Validator { get; set; }
public bool Editable { get; set; } = true; public bool Editable { get; set; } = true;
public bool Creatable { get; set; } = true; public bool Creatable { get; set; } = true;
@@ -74,7 +74,6 @@ public class PropertyConfigurator<TProp>(PropertyConfig config) {
/// <summary> /// <summary>
/// Determines if the value that should be displayed instead of the string representation of the type /// Determines if the value that should be displayed instead of the string representation of the type
/// </summary> /// </summary>
/// <seealso cref="Format"/>
public PropertyConfigurator<TProp> SetDisplayedProperty<TInnerProp>(Expression<Func<TProp, TInnerProp>> propertyExpression) { public PropertyConfigurator<TProp> SetDisplayedProperty<TInnerProp>(Expression<Func<TProp, TInnerProp>> propertyExpression) {
InnerConfig.DisplayedProperty = TableConfigurator<TProp>.GetPropertyInfo(propertyExpression); InnerConfig.DisplayedProperty = TableConfigurator<TProp>.GetPropertyInfo(propertyExpression);
return this; return this;
@@ -83,9 +82,14 @@ public class PropertyConfigurator<TProp>(PropertyConfig config) {
/// <summary> /// <summary>
/// Determines the value that's displayed in the admin ui /// Determines the value that's displayed in the admin ui
/// </summary> /// </summary>
/// <seealso cref="FormatEach{TInnerProp}"/>
/// <seealso cref="SetDisplayedProperty{TInnerProp}"/> /// <seealso cref="SetDisplayedProperty{TInnerProp}"/>
public PropertyConfigurator<TProp> Format(Func<TProp, IServiceProvider, string> formatter) { public PropertyConfigurator<TProp> Format(Func<TProp, IServiceProvider, string> formatter) {
InnerConfig.Formatter = (obj, provider) => Task.FromResult(formatter.Invoke((TProp)obj, provider));
return this;
}
/// <inheritdoc cref="Format(System.Func{TProp,System.IServiceProvider,string})"/>
public PropertyConfigurator<TProp> Format(Func<TProp, IServiceProvider, Task<string>> formatter) {
InnerConfig.Formatter = (obj, provider) => formatter.Invoke((TProp)obj, provider); InnerConfig.Formatter = (obj, provider) => formatter.Invoke((TProp)obj, provider);
return this; return this;
} }
@@ -94,6 +98,12 @@ public class PropertyConfigurator<TProp>(PropertyConfig config) {
/// Determines the value that's displayed for each entry in the list /// Determines the value that's displayed for each entry in the list
/// </summary> /// </summary>
public PropertyConfigurator<TProp> FormatEach<TInnerProp>(Func<TInnerProp, IServiceProvider, string> formatter) { public PropertyConfigurator<TProp> FormatEach<TInnerProp>(Func<TInnerProp, IServiceProvider, string> formatter) {
InnerConfig.EnumerableFormatter = (obj, provider) => Task.FromResult(formatter.Invoke((TInnerProp)obj, provider));
return this;
}
/// <inheritdoc cref="FormatEach{TInnerProp}(System.Func{TInnerProp,System.IServiceProvider,string})"/>
public PropertyConfigurator<TProp> FormatEach<TInnerProp>(Func<TInnerProp, IServiceProvider, Task<string>> formatter) {
InnerConfig.EnumerableFormatter = (obj, provider) => formatter.Invoke((TInnerProp)obj, provider); InnerConfig.EnumerableFormatter = (obj, provider) => formatter.Invoke((TInnerProp)obj, provider);
return this; return this;
} }
@@ -102,7 +112,13 @@ public class PropertyConfigurator<TProp>(PropertyConfig config) {
/// Determines the function used for parsing the value provided in the editor dialog to the actual property value /// Determines the function used for parsing the value provided in the editor dialog to the actual property value
/// </summary> /// </summary>
public PropertyConfigurator<TProp> SetParser(Func<string, IServiceProvider, TProp> parser) { public PropertyConfigurator<TProp> SetParser(Func<string, IServiceProvider, TProp> parser) {
InnerConfig.Parser = (str, provider) => parser.Invoke(str, provider)!; InnerConfig.Parser = (str, provider) => Task.FromResult<object>(parser.Invoke(str, provider)!);
return this;
}
/// <inheritdoc cref="SetParser(System.Func{string,System.IServiceProvider,TProp})"/>
public PropertyConfigurator<TProp> SetParser(Func<string, IServiceProvider, Task<TProp>> parser) {
InnerConfig.Parser = async (str, provider) => (await parser.Invoke(str, provider))!;
return this; return this;
} }

View File

@@ -99,6 +99,17 @@ public class TableConfigurator<TModel>(TableConfig config) {
/// <returns>The configurator for the virtual property</returns> /// <returns>The configurator for the virtual property</returns>
/// <seealso cref="PropertyConfigurator{TProp}"/> /// <seealso cref="PropertyConfigurator{TProp}"/>
public PropertyConfigurator<string> AddVirtualProperty(string name, Func<TModel, IServiceProvider, string> template) { public PropertyConfigurator<string> AddVirtualProperty(string name, Func<TModel, IServiceProvider, string> template) {
var prop = new PropertyConfig(InnerConfig.Properties.First().Info, InnerConfig, InnerConfig.Properties.Count) {
Name = name,
IsListingProperty = true,
Formatter = (obj, provider) => Task.FromResult(template.Invoke((TModel)obj, provider))
};
InnerConfig.Properties.Add(prop);
return new PropertyConfigurator<string>(prop);
}
/// <inheritdoc cref="AddVirtualProperty(string,System.Func{TModel,System.IServiceProvider,string})"/>
public PropertyConfigurator<string> AddVirtualProperty(string name, Func<TModel, IServiceProvider, Task<string>> template) {
var prop = new PropertyConfig(InnerConfig.Properties.First().Info, InnerConfig, InnerConfig.Properties.Count) { var prop = new PropertyConfig(InnerConfig.Properties.First().Info, InnerConfig, InnerConfig.Properties.Count) {
Name = name, Name = name,
IsListingProperty = true, IsListingProperty = true,

View File

@@ -12,5 +12,5 @@ public interface ITableManager {
public Task AddItem(object item); public Task AddItem(object item);
public Task RevertChanges(object item); public Task RevertChanges(object item);
public string DisplayProperty(object? item, PropertyConfig prop, object? value = null); public Task<string> DisplayProperty(object? item, PropertyConfig prop, object? value = null);
} }

View File

@@ -66,24 +66,24 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
return false; return false;
} }
public string DisplayProperty(object? item, PropertyConfig prop, object? value = null) { public async Task<string> DisplayProperty(object? item, PropertyConfig prop, object? value = null) {
if (item is null) return string.Empty; if (item is null) return string.Empty;
if (prop.IsListingProperty) if (prop.IsListingProperty)
return prop.Formatter!.Invoke(item, provider); return await prop.Formatter!.Invoke(item, provider);
var propValue = value ?? prop.Info.GetValue(item); var propValue = value ?? prop.Info.GetValue(item);
if (propValue is null) if (propValue is null)
return string.Empty; return string.Empty;
if (prop.Formatter is not null) { if (prop.Formatter is not null) {
return prop.Formatter.Invoke(propValue, provider); return await prop.Formatter.Invoke(propValue, provider);
} }
if (prop.IsEnumerable) { if (prop.IsEnumerable) {
if (value is not null) { if (value is not null) {
if (prop.EnumerableFormatter is not null) { if (prop.EnumerableFormatter is not null) {
return prop.EnumerableFormatter.Invoke(value, provider); return await prop.EnumerableFormatter.Invoke(value, provider);
} }
return value.ToString() ?? string.Empty; return value.ToString() ?? string.Empty;
@@ -103,11 +103,11 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
var innerConfig = explorer.GetTable(propValue.GetType()); var innerConfig = explorer.GetTable(propValue.GetType());
if (innerConfig is null) return propValue.ToString()!; if (innerConfig is null) return propValue.ToString()!;
var innerProp = innerConfig!.Properties var innerProp = innerConfig.Properties
.SingleOrDefault(p => p.Info == prop.DisplayedProperty && !p.IsListingProperty); .SingleOrDefault(p => p.Info == prop.DisplayedProperty && !p.IsListingProperty);
if (innerProp is null) return propValue.ToString() ?? string.Empty; if (innerProp is null) return propValue.ToString() ?? string.Empty;
return DisplayProperty(propValue, innerProp); return await DisplayProperty(propValue, innerProp);
} }
private IQueryable<TModel> IncludeForeignKeys(IQueryable<TModel> query) { private IQueryable<TModel> IncludeForeignKeys(IQueryable<TModel> query) {

View File

@@ -201,7 +201,7 @@
if (Content.CurrentObject is null) return default; if (Content.CurrentObject is null) return default;
if (listItem is not null) { if (listItem is not null) {
return (TValue)(object)_manager!.DisplayProperty(Content.CurrentObject, config, listItem); return (TValue)(object)_manager!.DisplayProperty(Content.CurrentObject, config, listItem).Result;
} }
var value = config.Info.GetValue(Content.CurrentObject); var value = config.Info.GetValue(Content.CurrentObject);
@@ -213,7 +213,7 @@
return (TValue)value; return (TValue)value;
if (typeof(TValue) == typeof(string)) if (typeof(TValue) == typeof(string))
return (TValue)(object)_manager!.DisplayProperty(Content.CurrentObject, config); return (TValue)(object)_manager!.DisplayProperty(Content.CurrentObject, config).Result;
return (TValue)Convert.ChangeType(value, typeof(TValue)); return (TValue)Convert.ChangeType(value, typeof(TValue));
} }
@@ -295,7 +295,7 @@
} }
if (config.Parser is not null && result is not null) { if (config.Parser is not null && result is not null) {
result = config.Parser(result.ToString()!, Provider); result = await config.Parser(result.ToString()!, Provider);
} }
if (needsOverride) if (needsOverride)

View File

@@ -54,7 +54,7 @@
@foreach (var property in _config!.Properties.Where(prop => prop.List).OrderBy(prop => prop.Order)) { @foreach (var property in _config!.Properties.Where(prop => prop.List).OrderBy(prop => prop.Order)) {
<PropertyColumn <PropertyColumn
Title="@property.Name" Property="o => _manager!.DisplayProperty(o, property, null)" Title="@property.Name" Property="o => _manager!.DisplayProperty(o, property, null).Result"
Style="min-width: max-content; height: 44px;" Style="min-width: max-content; height: 44px;"
Sortable="@property.Sortable"/> Sortable="@property.Sortable"/>
} }

View File

@@ -23,63 +23,63 @@ public class DisplayPropertyTests {
} }
[Fact] [Fact]
public void DisplayProperty_ReturnsEmptyString_WhenItemIsNull() { public async Task DisplayProperty_ReturnsEmptyString_WhenItemIsNull() {
// Arrange // Arrange
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0); var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0);
// Act // Act
var result = _tableManager.DisplayProperty(null, prop); var result = await _tableManager.DisplayProperty(null, prop);
// Assert // Assert
Assert.Equal(string.Empty, result); Assert.Equal(string.Empty, result);
} }
[Fact] [Fact]
public void DisplayProperty_UsesFormatter_WhenListingProperty() { public async Task DisplayProperty_UsesFormatter_WhenListingProperty() {
// Arrange // Arrange
var item = "test"; var item = "test";
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0) { var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0) {
IsListingProperty = true, IsListingProperty = true,
Formatter = (obj, provider) => ((string)obj).ToUpper() Formatter = (obj, provider) => Task.FromResult(((string)obj).ToUpper())
}; };
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("TEST", result); Assert.Equal("TEST", result);
} }
[Fact] [Fact]
public void DisplayProperty_UsesValueFormatter_WhenNotListingProperty() { public async Task DisplayProperty_UsesValueFormatter_WhenNotListingProperty() {
// Arrange // Arrange
var item = "test"; var item = "test";
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0) { var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0) {
Formatter = (obj, provider) => ((int)obj).ToString("D4") Formatter = (obj, provider) => Task.FromResult(((int)obj).ToString("D4"))
}; };
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("0004", result); Assert.Equal("0004", result);
} }
[Fact] [Fact]
public void DisplayProperty_ReturnsValueAsString_WhenNoFormatter() { public async Task DisplayProperty_ReturnsValueAsString_WhenNoFormatter() {
// Arrange // Arrange
var item = "test"; var item = "test";
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0); var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0);
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("4", result); Assert.Equal("4", result);
} }
[Fact] [Fact]
public void DisplayProperty_ReturnsEnumerableCount_WhenEnumerableProperty() { public async Task DisplayProperty_ReturnsEnumerableCount_WhenEnumerableProperty() {
// Arrange // Arrange
var item = new { List = new List<int> { 1, 2, 3 } }; var item = new { List = new List<int> { 1, 2, 3 } };
var prop = new PropertyConfig(item.GetType().GetProperty("List")!, _config, 0) { var prop = new PropertyConfig(item.GetType().GetProperty("List")!, _config, 0) {
@@ -87,14 +87,14 @@ public class DisplayPropertyTests {
}; };
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("3", result); Assert.Equal("3", result);
} }
[Fact] [Fact]
public void DisplayProperty_UsesDisplayedProperty_WhenNoDirectFormatter() { public async Task DisplayProperty_UsesDisplayedProperty_WhenNoDirectFormatter() {
// Arrange // Arrange
var item = new { Inner = new { Key = 42 } }; var item = new { Inner = new { Key = 42 } };
var innerPropInfo = item.Inner.GetType().GetProperty("Key"); var innerPropInfo = item.Inner.GetType().GetProperty("Key");
@@ -111,43 +111,43 @@ public class DisplayPropertyTests {
}); });
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("42", result); Assert.Equal("42", result);
} }
[Fact] [Fact]
public void DisplayProperty_ReturnsEmptyString_WhenPropValueIsNull() { public async Task DisplayProperty_ReturnsEmptyString_WhenPropValueIsNull() {
// Arrange // Arrange
var item = new { Name = (string?)null }; var item = new { Name = (string?)null };
var prop = new PropertyConfig(item.GetType().GetProperty("Name")!, _config, 0); var prop = new PropertyConfig(item.GetType().GetProperty("Name")!, _config, 0);
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal(string.Empty, result); Assert.Equal(string.Empty, result);
} }
[Fact] [Fact]
public void DisplayProperty_UsesEnumerableFormatter_WhenEnumerableAndValueProvided() { public async Task DisplayProperty_UsesEnumerableFormatter_WhenEnumerableAndValueProvided() {
// Arrange // Arrange
var item = new { List = new List<int> { 1, 2, 3 } }; var item = new { List = new List<int> { 1, 2, 3 } };
var prop = new PropertyConfig(item.GetType().GetProperty("List")!, _config, 0) { var prop = new PropertyConfig(item.GetType().GetProperty("List")!, _config, 0) {
IsEnumerable = true, IsEnumerable = true,
EnumerableFormatter = (obj, provider) => string.Join(",", ((IEnumerable<int>)obj)) EnumerableFormatter = (obj, provider) => Task.FromResult(string.Join(",", ((IEnumerable<int>)obj)))
}; };
// Act // Act
var result = _tableManager.DisplayProperty(item, prop, item.List); var result = await _tableManager.DisplayProperty(item, prop, item.List);
// Assert // Assert
Assert.Equal("1,2,3", result); Assert.Equal("1,2,3", result);
} }
[Fact] [Fact]
public void DisplayProperty_ReturnsEmptyString_WhenDisplayedPropertyAndInnerConfigIsNull() { public async Task DisplayProperty_ReturnsEmptyString_WhenDisplayedPropertyAndInnerConfigIsNull() {
// Arrange // Arrange
var item = new { Inner = new { Key = 42 } }; var item = new { Inner = new { Key = 42 } };
var innerPropInfo = item.Inner.GetType().GetProperty("Key"); var innerPropInfo = item.Inner.GetType().GetProperty("Key");
@@ -161,14 +161,14 @@ public class DisplayPropertyTests {
.Returns((TableConfig?)null); .Returns((TableConfig?)null);
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("{ Key = 42 }", result); // Returns the value as string if inner config is null Assert.Equal("{ Key = 42 }", result); // Returns the value as string if inner config is null
} }
[Fact] [Fact]
public void DisplayProperty_ReturnsKeyValue_WhenDisplayedPropertyIsNull() { public async Task DisplayProperty_ReturnsKeyValue_WhenDisplayedPropertyIsNull() {
// Arrange // Arrange
var item = new { Inner = new { Key = 42 } }; var item = new { Inner = new { Key = 42 } };
var propInfo = item.GetType().GetProperty("Inner"); var propInfo = item.GetType().GetProperty("Inner");
@@ -177,21 +177,21 @@ public class DisplayPropertyTests {
var keyProperty = item.Inner.GetType().GetProperty("Key"); var keyProperty = item.Inner.GetType().GetProperty("Key");
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("{ Key = 42 }", result); // Returns key value as string if DisplayedProperty is null Assert.Equal("{ Key = 42 }", result); // Returns key value as string if DisplayedProperty is null
} }
[Fact] [Fact]
public void DisplayProperty_ReturnsToStringValue_WhenNoKeyOrDisplayedProperty() { public async Task DisplayProperty_ReturnsToStringValue_WhenNoKeyOrDisplayedProperty() {
// Arrange // Arrange
var item = new { Inner = new { Name = "Test" } }; var item = new { Inner = new { Name = "Test" } };
var propInfo = item.GetType().GetProperty("Inner"); var propInfo = item.GetType().GetProperty("Inner");
var prop = new PropertyConfig(propInfo!, _config, 0); var prop = new PropertyConfig(propInfo!, _config, 0);
// Act // Act
var result = _tableManager.DisplayProperty(item, prop); var result = await _tableManager.DisplayProperty(item, prop);
// Assert // Assert
Assert.Equal("{ Name = Test }", result); // Returns ToString value of inner property Assert.Equal("{ Name = Test }", result); // Returns ToString value of inner property