Added more tests

This commit is contained in:
2025-01-19 12:12:01 +01:00
parent afe8a41f6c
commit 8d63910aae
14 changed files with 529 additions and 60 deletions

View File

@@ -10,24 +10,20 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment=""> <list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/DbContextConfiguratorTests.cs" afterDir="false" /> <change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Services/DisplayPropertyTests.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/HopFrameConfiguratorTests.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/PropertyConfiguratorTests.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/TableConfiguratorTests.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/HopFrame.Core.Tests.csproj" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/MockDbContext.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/MockModel.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/MockModel2.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/QueryProvider.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Services/ContextExplorerTests.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Services/TableManagerTests.cs" 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 beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/HopFrame.sln" beforeDir="false" afterPath="$PROJECT_DIR$/HopFrame.sln" 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 beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfigurator.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfig.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 beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/HopFrameConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/HopFrameConfig.cs" 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 beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfigurator.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfig.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Web/HopFrame.Web.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/HopFrame.Web.csproj" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfigurator.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfig.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Web/ServiceCollectionExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/ServiceCollectionExtensions.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/HopFrame.Core.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/HopFrame.Core.csproj" afterDir="false" /> <change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/HopFrameConfiguratorTests.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/HopFrameConfiguratorTests.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/PropertyConfiguratorTests.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/PropertyConfiguratorTests.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/TableConfiguratorTests.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Config/TableConfiguratorTests.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/MockModel.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/MockModel.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/MockModel2.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Models/MockModel2.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Services/ContextExplorerTests.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Core.Tests/Services/ContextExplorerTests.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" />
@@ -94,26 +90,26 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
".NET Launch Settings Profile.HopFrame.Testing.executor": "Run", &quot;.NET Launch Settings Profile.HopFrame.Testing.executor&quot;: &quot;Run&quot;,
".NET Launch Settings Profile.HopFrame.Testing: https.executor": "Run", &quot;.NET Launch Settings Profile.HopFrame.Testing: https.executor&quot;: &quot;Run&quot;,
".NET Project.HopFrame.Testing.executor": "Run", &quot;.NET Project.HopFrame.Testing.executor&quot;: &quot;Run&quot;,
"72b118b0-a6fc-4561-acdf-74f0b454dbb8.executor": "Debug", &quot;72b118b0-a6fc-4561-acdf-74f0b454dbb8.executor&quot;: &quot;Debug&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.git.unshallow": "true", &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
"dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug", &quot;dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor&quot;: &quot;Debug&quot;,
"git-widget-placeholder": "!18 on feature/unit-tests", &quot;git-widget-placeholder&quot;: &quot;!18 on feature/unit-tests&quot;,
"list.type.of.created.stylesheet": "CSS", &quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"settings.editor.selected.configurable": "preferences.environmentSetup", &quot;settings.editor.selected.configurable&quot;: &quot;preferences.environmentSetup&quot;,
"vue.rearranger.settings.migration": "true" &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}]]></component> }</component>
<component name="RunManager" selected=".NET Launch Settings Profile.HopFrame.Testing: https"> <component name="RunManager" selected=".NET Launch Settings Profile.HopFrame.Testing: https">
<configuration name="HopFrame.Testing: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile"> <configuration name="HopFrame.Testing: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/testing/HopFrame.Testing/HopFrame.Testing.csproj" /> <option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/testing/HopFrame.Testing/HopFrame.Testing.csproj" />
@@ -171,7 +167,8 @@
<workItem from="1737047730756" duration="7678000" /> <workItem from="1737047730756" duration="7678000" />
<workItem from="1737120164342" duration="9351000" /> <workItem from="1737120164342" duration="9351000" />
<workItem from="1737199714142" duration="8344000" /> <workItem from="1737199714142" duration="8344000" />
<workItem from="1737208313207" duration="3856000" /> <workItem from="1737208313207" duration="4612000" />
<workItem from="1737281957060" duration="2760000" />
</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" />
@@ -301,12 +298,80 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1737208088933</updated> <updated>1737208088933</updated>
</task> </task>
<option name="localTasksCounter" value="17" /> <task id="LOCAL-00017" summary="Created tests for the core module">
<option name="closed" value="true" />
<created>1737212497960</created>
<option name="number" value="00017" />
<option name="presentableId" value="LOCAL-00017" />
<option name="project" value="LOCAL" />
<updated>1737212497960</updated>
</task>
<option name="localTasksCounter" value="18" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>
<component name="UnitTestsCoverage.Settings">
<option name="coveragePercentColumnWidth" value="131" />
<option name="sortOrder" value="DESCENDING" />
<option name="sortedColumn" value="1" />
<option name="symbolColumnWidth" value="457" />
<coverage-tree-state>
<expand>
<path>
<item name="rootNode" type="c53c71d1:RiderDotCoverCoverageTreeModel$RootNode" />
<item name="Total 59% 577/1420" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
</path>
<path>
<item name="rootNode" type="c53c71d1:RiderDotCoverCoverageTreeModel$RootNode" />
<item name="Total 59% 577/1420" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Core 45% 407/741" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
</path>
<path>
<item name="rootNode" type="c53c71d1:RiderDotCoverCoverageTreeModel$RootNode" />
<item name="Total 59% 577/1420" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Core 45% 407/741" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
</path>
<path>
<item name="rootNode" type="c53c71d1:RiderDotCoverCoverageTreeModel$RootNode" />
<item name="Total 59% 577/1420" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Core 45% 407/741" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
</path>
<path>
<item name="rootNode" type="c53c71d1:RiderDotCoverCoverageTreeModel$RootNode" />
<item name="Total 59% 577/1420" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Core 45% 407/741" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Services.Implementations 93% 9/134" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
</path>
<path>
<item name="rootNode" type="c53c71d1:RiderDotCoverCoverageTreeModel$RootNode" />
<item name="Total 59% 577/1420" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Core 45% 407/741" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Services.Implementations 93% 9/134" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="TableManager&lt;TModel&gt; 87% 8/63" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
</path>
<path>
<item name="rootNode" type="c53c71d1:RiderDotCoverCoverageTreeModel$RootNode" />
<item name="Total 59% 577/1420" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Core 45% 407/741" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="HopFrame.Core 92% 31/365" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="Services.Implementations 93% 9/134" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="TableManager&lt;TModel&gt; 87% 8/63" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
<item name="DisplayProperty(object,PropertyConfig,object) 73% 7/26" type="8b8fad3a:RiderDotCoverCoverageTreeNode" />
</path>
</expand>
<select />
</coverage-tree-state>
</component>
<component name="UnityCheckinConfiguration" checkUnsavedScenes="true" /> <component name="UnityCheckinConfiguration" checkUnsavedScenes="true" />
<component name="UnityProjectConfiguration" hasMinimizedUI="false" /> <component name="UnityProjectConfiguration" hasMinimizedUI="false" />
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
@@ -327,6 +392,7 @@
<MESSAGE value="Added text area support and DI support for modifier functions" /> <MESSAGE value="Added text area support and DI support for modifier functions" />
<MESSAGE value="Addressed all build warnings" /> <MESSAGE value="Addressed all build warnings" />
<MESSAGE value="Added documentation for the configurators and service extensions methods" /> <MESSAGE value="Added documentation for the configurators and service extensions methods" />
<option name="LAST_COMMIT_MESSAGE" value="Added documentation for the configurators and service extensions methods" /> <MESSAGE value="Created tests for the core module" />
<option name="LAST_COMMIT_MESSAGE" value="Created tests for the core module" />
</component> </component>
</project> </project>

View File

@@ -20,9 +20,9 @@ public class PropertyConfig(PropertyInfo info, TableConfig table, int nthPropert
public bool DisplayValue { get; set; } = true; public bool DisplayValue { get; set; } = true;
public bool TextArea { get; set; } public bool TextArea { get; set; }
public int TextAreaRows { get; set; } = 16; public int TextAreaRows { get; set; } = 16;
public bool IsRelation { get; set; } public bool IsRelation { get; internal set; }
public bool IsRequired { get; set; } public bool IsRequired { get; internal set; }
public bool IsEnumerable { get; set; } public bool IsEnumerable { get; internal set; }
public bool IsListingProperty { get; set; } public bool IsListingProperty { get; set; }
public int Order { get; set; } = nthProperty; public int Order { get; set; } = nthProperty;
} }

View File

@@ -99,10 +99,11 @@ 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); var prop = new PropertyConfig(InnerConfig.Properties.First().Info, InnerConfig, InnerConfig.Properties.Count) {
prop.Name = name; Name = name,
prop.IsListingProperty = true; IsListingProperty = true,
prop.Formatter = (obj, provider) => template.Invoke((TModel)obj, provider); Formatter = (obj, provider) => template.Invoke((TModel)obj, provider)
};
InnerConfig.Properties.Add(prop); InnerConfig.Properties.Add(prop);
return new PropertyConfigurator<string>(prop); return new PropertyConfigurator<string>(prop);
} }

View File

@@ -9,7 +9,7 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
public IQueryable<object> LoadPage(int page, int perPage = 20) { public IQueryable<object> LoadPage(int page, int perPage = 20) {
var table = context.Set<TModel>(); var table = context.Set<TModel>();
var data = IncludeForgeinKeys(table); var data = IncludeForeignKeys(table);
return data return data
.Skip(page * perPage) .Skip(page * perPage)
.Take(perPage); .Take(perPage);
@@ -17,7 +17,7 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
public Task<(IEnumerable<object>, int)> Search(string searchTerm, int page = 0, int perPage = 20) { public Task<(IEnumerable<object>, int)> Search(string searchTerm, int page = 0, int perPage = 20) {
var table = context.Set<TModel>(); var table = context.Set<TModel>();
var all = IncludeForgeinKeys(table) var all = IncludeForeignKeys(table)
.AsEnumerable() .AsEnumerable()
.Where(item => ItemSearched(item, searchTerm)) .Where(item => ItemSearched(item, searchTerm))
.ToList(); .ToList();
@@ -101,6 +101,8 @@ 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()!;
var innerProp = innerConfig!.Properties var innerProp = innerConfig!.Properties
.SingleOrDefault(p => p.Info == prop.DisplayedProperty && !p.IsListingProperty); .SingleOrDefault(p => p.Info == prop.DisplayedProperty && !p.IsListingProperty);
@@ -108,14 +110,10 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
return DisplayProperty(propValue, innerProp); return DisplayProperty(propValue, innerProp);
} }
private IQueryable<TModel> IncludeForgeinKeys(IQueryable<TModel> query) { private IQueryable<TModel> IncludeForeignKeys(IQueryable<TModel> query) {
var pendingQuery = query; return config.Properties
.Where(prop => prop.IsRelation)
foreach (var property in config.Properties.Where(prop => prop.IsRelation)) { .Aggregate(query, (current, property) => current.Include(property.Info.Name));
pendingQuery = pendingQuery.Include(property.Info.Name);
}
return pendingQuery;
} }
} }

View File

@@ -11,6 +11,10 @@
<SupportedPlatform Include="browser"/> <SupportedPlatform Include="browser"/>
</ItemGroup> </ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0"/> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0"/>
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.11.2" /> <PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.11.2" />

View File

@@ -1,7 +1,9 @@
using HopFrame.Core; using HopFrame.Core;
using HopFrame.Core.Config; using HopFrame.Core.Config;
using HopFrame.Web.Components.Pages;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.FluentUI.AspNetCore.Components; using Microsoft.FluentUI.AspNetCore.Components;
using Microsoft.AspNetCore.Builder;
namespace HopFrame.Web; namespace HopFrame.Web;
@@ -34,4 +36,11 @@ public static class ServiceCollectionExtensions {
return services; return services;
} }
public static RazorComponentsEndpointConventionBuilder MapHopFramePages(this RazorComponentsEndpointConventionBuilder builder) {
builder
.AddInteractiveServerRenderMode()
.AddAdditionalAssemblies(typeof(HopFrameHome).Assembly);
return builder;
}
} }

View File

@@ -94,6 +94,6 @@ app.UseAntiforgery();
app.MapStaticAssets(); app.MapStaticAssets();
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode() .AddInteractiveServerRenderMode()
.AddAdditionalAssemblies(typeof(HopFrameHome).Assembly); .MapHopFramePages();
app.Run(); app.Run();

View File

@@ -19,6 +19,24 @@ public class HopFrameConfiguratorTests {
Assert.IsType<DbContextConfigurator<MockDbContext>>(dbContextConfigurator); Assert.IsType<DbContextConfigurator<MockDbContext>>(dbContextConfigurator);
} }
[Fact]
public void AddDbContext_WithConfigurator_AddsDbContextToInnerConfig() {
// Arrange
var config = new HopFrameConfig();
var configurator = new HopFrameConfigurator(config);
// Act
object dbContextConfigurator = null!;
configurator.AddDbContext<MockDbContext>(context => {
dbContextConfigurator = context;
});
// Assert
Assert.Single(config.Contexts);
Assert.IsType<DbContextConfig>(config.Contexts[0]);
Assert.IsType<DbContextConfigurator<MockDbContext>>(dbContextConfigurator);
}
[Fact] [Fact]
public void DisplayUserInfo_SetsDisplayUserInfoProperty() { public void DisplayUserInfo_SetsDisplayUserInfoProperty() {
// Arrange // Arrange

View File

@@ -209,4 +209,17 @@ public class PropertyConfiguratorTests {
// Assert // Assert
Assert.Equal(orderIndex, propertyConfig.Order); Assert.Equal(orderIndex, propertyConfig.Order);
} }
[Fact]
public void Constructor_SetsTableProperty() {
// Arrange
var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
// Act
var propertyConfig = new PropertyConfig(typeof(MockModel).GetProperty("Id")!, tableConfig, 0);
// Assert
Assert.NotNull(propertyConfig.Table);
Assert.Equal(tableConfig, propertyConfig.Table);
}
} }

View File

@@ -33,6 +33,22 @@ public class TableConfiguratorTests {
Assert.IsType<PropertyConfigurator<int>>(propertyConfigurator); Assert.IsType<PropertyConfigurator<int>>(propertyConfigurator);
} }
public void Property_WithConfigurator_ReturnsCorrectPropertyConfigurator() {
// Arrange
var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
var configurator = new TableConfigurator<MockModel>(tableConfig);
Expression<Func<MockModel, int>> propertyExpression = model => model.Id;
// Act
object propertyConfigurator = null!;
configurator.Property(propertyExpression, c => {
propertyConfigurator = c;
});
// Assert
Assert.IsType<PropertyConfigurator<int>>(propertyConfigurator);
}
[Fact] [Fact]
public void AddVirtualProperty_AddsVirtualPropertyToConfig() { public void AddVirtualProperty_AddsVirtualPropertyToConfig() {
// Arrange // Arrange
@@ -51,6 +67,27 @@ public class TableConfiguratorTests {
Assert.Equal("VirtualName", virtualProperty.Name); Assert.Equal("VirtualName", virtualProperty.Name);
} }
[Fact]
public void AddVirtualProperty_WithConfigurator_AddsVirtualPropertyToConfig() {
// Arrange
var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "MockModels", 0);
var configurator = new TableConfigurator<MockModel>(tableConfig);
Func<MockModel, IServiceProvider, string> template = (model, _) => model.Name!;
// Act
object propertyConfigurator = null!;
configurator.AddVirtualProperty("VirtualName", template, c => {
propertyConfigurator = c;
});
// Assert
var virtualProperty = tableConfig.Properties.SingleOrDefault(p => p.Name == "VirtualName");
Assert.NotNull(virtualProperty);
Assert.NotNull(propertyConfigurator);
Assert.True(virtualProperty.IsListingProperty);
Assert.Equal("VirtualName", virtualProperty.Name);
}
[Fact] [Fact]
public void SetDisplayName_SetsDisplayNameProperty() { public void SetDisplayName_SetsDisplayNameProperty() {
// Arrange // Arrange
@@ -148,4 +185,27 @@ public class TableConfiguratorTests {
// Assert // Assert
Assert.Equal(policy, tableConfig.DeletePolicy); Assert.Equal(policy, tableConfig.DeletePolicy);
} }
[Fact]
public void Constructor_WithKeyProperty_DisablesEdit() {
// Act
var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel2), "Models2", 0);
var prop = tableConfig.Properties.SingleOrDefault(prop => prop.Info.Name == nameof(MockModel2.Id));
// Assert
Assert.NotNull(prop);
Assert.False(prop.Editable);
}
[Fact]
public void Constructor_WithGeneratedProperty_DisablesEditAndCreate() {
// Act
var tableConfig = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel2), "Models2", 0);
var prop = tableConfig.Properties.SingleOrDefault(prop => prop.Info.Name == nameof(MockModel2.Number));
// Assert
Assert.NotNull(prop);
Assert.False(prop.Editable);
Assert.False(prop.Creatable);
}
} }

View File

@@ -1,7 +1,12 @@
namespace HopFrame.Core.Tests.Models; using System.ComponentModel.DataAnnotations.Schema;
namespace HopFrame.Core.Tests.Models;
// A mock model for testing purposes // A mock model for testing purposes
public class MockModel { public class MockModel {
public int Id { get; set; } public int Id { get; set; }
public string? Name { get; set; } public string? Name { get; set; }
[ForeignKey("other")]
public List<MockModel2> Model2 { get; set; }
} }

View File

@@ -1,5 +1,12 @@
namespace HopFrame.Core.Tests.Models; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace HopFrame.Core.Tests.Models;
public class MockModel2 { public class MockModel2 {
public string Id { get; set; } [Key]
public required string Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Number { get; set; }
} }

View File

@@ -50,6 +50,26 @@ public class ContextExplorerTests {
Assert.Equal(tableConfig, result); Assert.Equal(tableConfig, result);
} }
[Fact]
public void GetTable_ByDisplayName_ReturnsNullIfTableNotFound() {
// Arrange
var config = new HopFrameConfig();
var contextConfig = new DbContextConfig(typeof(MockDbContext));
var tableConfig = contextConfig.Tables[0];
config.Contexts.Add(contextConfig);
tableConfig.DisplayName = "TestTable";
var provider = new Mock<IServiceProvider>();
provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(new MockDbContext());
var contextExplorer = new ContextExplorer(config, provider.Object, new Logger<ContextExplorer>(new LoggerFactory()));
// Act
var result = contextExplorer.GetTable("InvalidTable");
// Assert
Assert.Null(result);
}
[Fact] [Fact]
public void GetTable_ByType_ReturnsCorrectTable() { public void GetTable_ByType_ReturnsCorrectTable() {
// Arrange // Arrange
@@ -70,6 +90,25 @@ public class ContextExplorerTests {
Assert.Equal(tableConfig, result); Assert.Equal(tableConfig, result);
} }
[Fact]
public void GetTable_ByType_ReturnsNullIfTableNotFound() {
// Arrange
var config = new HopFrameConfig();
var contextConfig = new DbContextConfig(typeof(MockDbContext));
var tableConfig = contextConfig.Tables[0];
config.Contexts.Add(contextConfig);
var provider = new Mock<IServiceProvider>();
provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(new MockDbContext());
var contextExplorer = new ContextExplorer(config, provider.Object, new Logger<ContextExplorer>(new LoggerFactory()));
// Act
var result = contextExplorer.GetTable(typeof(ContextExplorerTests));
// Assert
Assert.Null(result);
}
[Fact] [Fact]
public void GetTableManager_ReturnsCorrectTableManager() { public void GetTableManager_ReturnsCorrectTableManager() {
// Arrange // Arrange
@@ -111,6 +150,27 @@ public class ContextExplorerTests {
Assert.Null(tableManager); Assert.Null(tableManager);
} }
[Fact]
public void GetTableManager_ReturnsNullIfTableNotFound() {
// Arrange
var config = new HopFrameConfig();
var contextConfig = new DbContextConfig(typeof(MockDbContext));
var tableConfig = new TableConfig(contextConfig, typeof(MockModel), "Models", 0);
contextConfig.Tables.Add(tableConfig);
config.Contexts.Add(contextConfig);
var dbContext = new MockDbContext();
var provider = new Mock<IServiceProvider>();
provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(dbContext);
var contextExplorer = new ContextExplorer(config, provider.Object, new Logger<ContextExplorer>(new LoggerFactory()));
// Act
var tableManager = contextExplorer.GetTableManager("InvalidTable");
// Assert
Assert.Null(tableManager);
}
[Fact] [Fact]
public void SeedTableData_SetsTableSeededFlag() { public void SeedTableData_SetsTableSeededFlag() {
// Arrange // Arrange
@@ -129,4 +189,33 @@ public class ContextExplorerTests {
// Assert // Assert
Assert.True(tableConfig.Seeded); Assert.True(tableConfig.Seeded);
} }
[Fact]
public void SeedTableData_SetsTablePropertiesCorrectly() {
// Arrange
var config = new HopFrameConfig();
var contextConfig = new DbContextConfig(typeof(MockDbContext));
var tableConfig = contextConfig.Tables[0];
var tableConfig2 = contextConfig.Tables[1];
config.Contexts.Add(contextConfig);
var provider = new Mock<IServiceProvider>();
provider.Setup(p => p.GetService(typeof(MockDbContext))).Returns(new MockDbContext());
var contextExplorer = new ContextExplorer(config, provider.Object, new Logger<ContextExplorer>(new LoggerFactory()));
// Act
contextExplorer.GetTable("Models");
contextExplorer.GetTable("Models2");
// Assert
var relationProp = tableConfig.Properties.SingleOrDefault(prop => prop.Info.Name == nameof(MockModel.Model2));
var keyProp = tableConfig2.Properties.SingleOrDefault(prop => prop.Info.Name == nameof(MockModel2.Id));
Assert.NotNull(relationProp);
Assert.NotNull(keyProp);
Assert.True(relationProp.IsRelation);
Assert.True(relationProp.IsEnumerable);
Assert.True(keyProp.IsRequired);
Assert.False(keyProp.IsRelation);
Assert.False(keyProp.IsEnumerable);
}
} }

View File

@@ -0,0 +1,199 @@
using HopFrame.Core.Config;
using HopFrame.Core.Services;
using HopFrame.Core.Services.Implementations;
using HopFrame.Core.Tests.Models;
using Microsoft.EntityFrameworkCore;
using Moq;
namespace HopFrame.Core.Tests.Services;
public class DisplayPropertyTests {
private readonly Mock<IServiceProvider> _providerMock;
private readonly Mock<IContextExplorer> _explorerMock;
private readonly TableConfig _config;
private readonly TableManager<object> _tableManager;
public DisplayPropertyTests() {
var contextMock = new Mock<DbContext>();
_providerMock = new Mock<IServiceProvider>();
_explorerMock = new Mock<IContextExplorer>();
_config = new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "Models", 0);
_tableManager =
new TableManager<object>(contextMock.Object, _config, _explorerMock.Object, _providerMock.Object);
}
[Fact]
public void DisplayProperty_ReturnsEmptyString_WhenItemIsNull() {
// Arrange
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0);
// Act
var result = _tableManager.DisplayProperty(null, prop);
// Assert
Assert.Equal(string.Empty, result);
}
[Fact]
public void DisplayProperty_UsesFormatter_WhenListingProperty() {
// Arrange
var item = "test";
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0) {
IsListingProperty = true,
Formatter = (obj, provider) => ((string)obj).ToUpper()
};
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("TEST", result);
}
[Fact]
public void DisplayProperty_UsesValueFormatter_WhenNotListingProperty() {
// Arrange
var item = "test";
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0) {
Formatter = (obj, provider) => ((int)obj).ToString("D4")
};
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("0004", result);
}
[Fact]
public void DisplayProperty_ReturnsValueAsString_WhenNoFormatter() {
// Arrange
var item = "test";
var prop = new PropertyConfig(typeof(string).GetProperty("Length")!, _config, 0);
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("4", result);
}
[Fact]
public void DisplayProperty_ReturnsEnumerableCount_WhenEnumerableProperty() {
// Arrange
var item = new { List = new List<int> { 1, 2, 3 } };
var prop = new PropertyConfig(item.GetType().GetProperty("List")!, _config, 0) {
IsEnumerable = true
};
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("3", result);
}
[Fact]
public void DisplayProperty_UsesDisplayedProperty_WhenNoDirectFormatter() {
// Arrange
var item = new { Inner = new { Key = 42 } };
var innerPropInfo = item.Inner.GetType().GetProperty("Key");
var innerPropConfig = new PropertyConfig(innerPropInfo!, _config, 0);
var propInfo = item.GetType().GetProperty("Inner");
var prop = new PropertyConfig(propInfo!, _config, 0) {
DisplayedProperty = innerPropInfo
};
_explorerMock
.Setup(e => e.GetTable(item.Inner.GetType()))
.Returns(new TableConfig(new DbContextConfig(typeof(MockDbContext)), typeof(MockModel), "Models", 0) {
Properties = { innerPropConfig }
});
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("42", result);
}
[Fact]
public void DisplayProperty_ReturnsEmptyString_WhenPropValueIsNull() {
// Arrange
var item = new { Name = (string?)null };
var prop = new PropertyConfig(item.GetType().GetProperty("Name")!, _config, 0);
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal(string.Empty, result);
}
[Fact]
public void DisplayProperty_UsesEnumerableFormatter_WhenEnumerableAndValueProvided() {
// Arrange
var item = new { List = new List<int> { 1, 2, 3 } };
var prop = new PropertyConfig(item.GetType().GetProperty("List")!, _config, 0) {
IsEnumerable = true,
EnumerableFormatter = (obj, provider) => string.Join(",", ((IEnumerable<int>)obj))
};
// Act
var result = _tableManager.DisplayProperty(item, prop, item.List);
// Assert
Assert.Equal("1,2,3", result);
}
[Fact]
public void DisplayProperty_ReturnsEmptyString_WhenDisplayedPropertyAndInnerConfigIsNull() {
// Arrange
var item = new { Inner = new { Key = 42 } };
var innerPropInfo = item.Inner.GetType().GetProperty("Key");
var propInfo = item.GetType().GetProperty("Inner");
var prop = new PropertyConfig(propInfo!, _config, 0) {
DisplayedProperty = innerPropInfo
};
_explorerMock
.Setup(e => e.GetTable(item.Inner.GetType()))
.Returns((TableConfig?)null);
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("{ Key = 42 }", result); // Returns the value as string if inner config is null
}
[Fact]
public void DisplayProperty_ReturnsKeyValue_WhenDisplayedPropertyIsNull() {
// Arrange
var item = new { Inner = new { Key = 42 } };
var propInfo = item.GetType().GetProperty("Inner");
var prop = new PropertyConfig(propInfo!, _config, 0);
var keyProperty = item.Inner.GetType().GetProperty("Key");
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("{ Key = 42 }", result); // Returns key value as string if DisplayedProperty is null
}
[Fact]
public void DisplayProperty_ReturnsToStringValue_WhenNoKeyOrDisplayedProperty() {
// Arrange
var item = new { Inner = new { Name = "Test" } };
var propInfo = item.GetType().GetProperty("Inner");
var prop = new PropertyConfig(propInfo!, _config, 0);
// Act
var result = _tableManager.DisplayProperty(item, prop);
// Assert
Assert.Equal("{ Name = Test }", result); // Returns ToString value of inner property
}
}