diff --git a/AutoCrudAdmin.Demo.sln b/AutoCrudAdmin.Demo.sln
index ce72b94..6085da8 100644
--- a/AutoCrudAdmin.Demo.sln
+++ b/AutoCrudAdmin.Demo.sln
@@ -18,6 +18,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{
Analyzers\stylecop.json = Analyzers\stylecop.json
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8D4B258F-4B65-42AC-9A42-0F2254F12CCC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoCrudAdmin.Tests", "tests\AutoCrudAdmin.Tests\AutoCrudAdmin.Tests.csproj", "{1FB1ED95-D654-4BA1-B73E-630714A99665}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoCrudAdmin.Demo.SqlServer.Tests", "tests\AutoCrudAdmin.Demo.SqlServer.Tests\AutoCrudAdmin.Demo.SqlServer.Tests.csproj", "{4526F658-4DE7-4B43-9461-6B43E8A6ADF3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -44,11 +50,21 @@ Global
{24C3C7CC-79CF-4498-A7F8-B4B4C4E0F4C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24C3C7CC-79CF-4498-A7F8-B4B4C4E0F4C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24C3C7CC-79CF-4498-A7F8-B4B4C4E0F4C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1FB1ED95-D654-4BA1-B73E-630714A99665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1FB1ED95-D654-4BA1-B73E-630714A99665}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1FB1ED95-D654-4BA1-B73E-630714A99665}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1FB1ED95-D654-4BA1-B73E-630714A99665}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4526F658-4DE7-4B43-9461-6B43E8A6ADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4526F658-4DE7-4B43-9461-6B43E8A6ADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4526F658-4DE7-4B43-9461-6B43E8A6ADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4526F658-4DE7-4B43-9461-6B43E8A6ADF3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{32BFD1FE-2923-468D-93C0-7FED0FCC2F77} = {54C80D9E-2664-431C-A8AC-0D490C119760}
{F9DC587B-E2AF-48D8-8A26-AACBC2F268D8} = {54C80D9E-2664-431C-A8AC-0D490C119760}
{4A584F16-FABE-4D30-AC34-958D04E5CDDE} = {54C80D9E-2664-431C-A8AC-0D490C119760}
{24C3C7CC-79CF-4498-A7F8-B4B4C4E0F4C9} = {54C80D9E-2664-431C-A8AC-0D490C119760}
+ {1FB1ED95-D654-4BA1-B73E-630714A99665} = {8D4B258F-4B65-42AC-9A42-0F2254F12CCC}
+ {4526F658-4DE7-4B43-9461-6B43E8A6ADF3} = {8D4B258F-4B65-42AC-9A42-0F2254F12CCC}
EndGlobalSection
EndGlobal
diff --git a/src/AutoCrudAdmin/AutoCrudAdmin.csproj b/src/AutoCrudAdmin/AutoCrudAdmin.csproj
index 1a03b3f..c0c96d1 100644
--- a/src/AutoCrudAdmin/AutoCrudAdmin.csproj
+++ b/src/AutoCrudAdmin/AutoCrudAdmin.csproj
@@ -1,75 +1,79 @@
-
- net6.0
- Library
- AutoCrudAdmin
- 1.1.26
- Doncho Minkov
- Minkov
- true
- true
- README.md
- https://github.com/Minkov/AutoCrudAdmin
- git
-
+
+ net6.0
+ Library
+ AutoCrudAdmin
+ 1.1.26
+ Doncho Minkov
+ Minkov
+ true
+ true
+ README.md
+ https://github.com/Minkov/AutoCrudAdmin
+ git
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
- <_ContentIncludedByDefault Remove="Pages\Admin\Index.cshtml"/>
- <_ContentIncludedByDefault Remove="Views\ProjectsAdmin\Index.cshtml"/>
- <_ContentIncludedByDefault Remove="wwwroot\css\mvc-grid.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\css\site.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\favicon.ico"/>
- <_ContentIncludedByDefault Remove="wwwroot\js\mvc-grid.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\js\site.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\LICENSE"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\jquery.validate.unobtrusive.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\jquery.validate.unobtrusive.min.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\LICENSE.txt"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\additional-methods.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\additional-methods.min.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\jquery.validate.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\jquery.validate.min.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\LICENSE.md"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.js"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.map"/>
- <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\LICENSE.txt"/>
-
+
+
+
-
-
-
+
+ <_ContentIncludedByDefault Remove="Pages\Admin\Index.cshtml"/>
+ <_ContentIncludedByDefault Remove="Views\ProjectsAdmin\Index.cshtml"/>
+ <_ContentIncludedByDefault Remove="wwwroot\css\mvc-grid.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\css\site.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\favicon.ico"/>
+ <_ContentIncludedByDefault Remove="wwwroot\js\mvc-grid.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\js\site.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\LICENSE"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\jquery.validate.unobtrusive.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\jquery.validate.unobtrusive.min.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\LICENSE.txt"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\additional-methods.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\additional-methods.min.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\jquery.validate.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\jquery.validate.min.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\LICENSE.md"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.js"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.map"/>
+ <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\LICENSE.txt"/>
+
+
+
+
+
diff --git a/src/AutoCrudAdmin/Helpers/ReflectionHelper.cs b/src/AutoCrudAdmin/Helpers/ReflectionHelper.cs
index 00ce5c9..e55cdc7 100644
--- a/src/AutoCrudAdmin/Helpers/ReflectionHelper.cs
+++ b/src/AutoCrudAdmin/Helpers/ReflectionHelper.cs
@@ -58,19 +58,19 @@ private static IEnumerable GetDbSetProperties()
.ToList();
var entityTypes = dbSetTypes
- .Select(dbSet => dbSet.PropertyType.GenericTypeArguments.FirstOrDefault())
+ .Select(dbSet => dbSet.PropertyType.GenericTypeArguments.First())
.Distinct()
.ToHashSet();
var uniqueEntityTypes = entityTypes
.Where(parent =>
- parent?.IsGenericParameter == true
- && entityTypes.All(child => child?.IsSubclassOfAnyType(parent) != true))
+ !parent.IsGenericParameter
+ && !entityTypes.Any(child => child.IsSubclassOfAnyType(parent)))
.ToHashSet();
return dbSetTypes
.DistinctBy(x => x.PropertyType.GenericTypeArguments.FirstOrDefault())
- .Where(x => uniqueEntityTypes.Contains(x.PropertyType.GenericTypeArguments.FirstOrDefault()))
+ .Where(x => uniqueEntityTypes.Contains(x.PropertyType.GenericTypeArguments.First()))
.OrderBy(x => x.Name)
.ToList();
}
diff --git a/src/AutoCrudAdmin/Views/Shared/MvcGrid/_Grid.cshtml b/src/AutoCrudAdmin/Views/Shared/MvcGrid/_Grid.cshtml
index d27f3af..7949761 100644
--- a/src/AutoCrudAdmin/Views/Shared/MvcGrid/_Grid.cshtml
+++ b/src/AutoCrudAdmin/Views/Shared/MvcGrid/_Grid.cshtml
@@ -10,14 +10,14 @@
}
-
+
@foreach (var row in rows)
{
-
+
@foreach (var column in Model.Columns)
{
var classes = (column.IsHidden ? column.CssClasses + " mvc-grid-hidden" : column.CssClasses).Trim();
diff --git a/tests/AutoCrudAdmin.Demo.SqlServer.Tests/AutoCrudAdmin.Demo.SqlServer.Tests.csproj b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/AutoCrudAdmin.Demo.SqlServer.Tests.csproj
new file mode 100644
index 0000000..2d4ea74
--- /dev/null
+++ b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/AutoCrudAdmin.Demo.SqlServer.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net6.0
+ enable
+ SA1600
+
+
+
+
+ Never
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Controllers/ProjectsControllerTests.cs b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Controllers/ProjectsControllerTests.cs
new file mode 100644
index 0000000..aa02c1a
--- /dev/null
+++ b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Controllers/ProjectsControllerTests.cs
@@ -0,0 +1,65 @@
+namespace AutoCrudAdmin.Demo.SqlServer.Tests.Controllers;
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using FluentAssertions;
+using Models.Models;
+using MyTested.AspNetCore.Mvc;
+using SqlServer.Controllers;
+using ViewModels;
+using Xunit;
+
+public class ProjectsControllerTests
+{
+ [Fact]
+ public void IndexShouldReturnDefaultViewWithCorrectModel()
+ => MyController
+ .Instance()
+ .Calling(c => c.Index())
+ .ShouldReturn()
+ .View("../AutoCrudAdmin/Index");
+
+ [Fact]
+ public void GetCreate_ReturnsViewResult()
+ => MyController
+ .Instance()
+ .Calling(c => c.Create(
+ new Dictionary(),
+ null))
+ .ShouldReturn()
+ .View("../AutoCrudAdmin/EntityForm");
+
+ [Theory]
+ [InlineData("Test Project")]
+ public void PostCreate_SavesAndRedirects(string name)
+ => MyPipeline
+ .Configuration()
+ .ShouldMap(request => request
+ .WithMethod(HttpMethod.Post)
+ .WithLocation("/Projects/PostCreate")
+ .WithAntiForgeryToken()
+ .WithFormFields(new Dictionary
+ {
+ { nameof(Project.Name), name },
+ { nameof(Project.OpenDate), DateTime.Now.ToString(CultureInfo.CurrentCulture) },
+ { nameof(Project.DueDate), DateTime.Now.AddDays(1).ToString(CultureInfo.CurrentCulture) },
+ }))
+ .To(c => c.PostCreate(
+ new Dictionary
+ {
+ { nameof(Project.Name), name },
+ { nameof(Project.OpenDate), DateTime.Now.ToString(CultureInfo.CurrentCulture) },
+ { nameof(Project.DueDate), DateTime.Now.AddDays(1).ToString(CultureInfo.CurrentCulture) },
+ },
+ new FormFilesContainer()))
+ .Which()
+ .ShouldHave()
+ .Data(data => data
+ .WithSet(set => set
+ .Should()
+ .ContainSingle(p => p.Name == name)))
+ .AndAlso()
+ .ShouldReturn()
+ .RedirectToAction("Index");
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Controllers/TasksControllerTests.cs b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Controllers/TasksControllerTests.cs
new file mode 100644
index 0000000..b6964af
--- /dev/null
+++ b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Controllers/TasksControllerTests.cs
@@ -0,0 +1,29 @@
+namespace AutoCrudAdmin.Demo.SqlServer.Tests.Controllers;
+
+using System.Collections.Generic;
+using MyTested.AspNetCore.Mvc;
+using SqlServer.Controllers;
+using ViewModels.Pages;
+using Xunit;
+
+public class TasksControllerTests
+{
+ [Fact]
+ public void IndexShouldReturnDefaultViewWithCorrectModel()
+ => MyController
+ .Instance()
+ .Calling(c => c.Index())
+ .ShouldReturn()
+ .View(view => view
+ .WithModelOfType());
+
+ [Fact]
+ public void GetCreate_ReturnsViewResult()
+ => MyController
+ .Instance()
+ .Calling(c => c.Create(
+ new Dictionary(),
+ null))
+ .ShouldReturn()
+ .View("../AutoCrudAdmin/EntityForm");
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Data/ProjectTestData.cs b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Data/ProjectTestData.cs
new file mode 100644
index 0000000..a5b084b
--- /dev/null
+++ b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Data/ProjectTestData.cs
@@ -0,0 +1,27 @@
+namespace AutoCrudAdmin.Demo.SqlServer.Tests.Data;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Bogus;
+using Models.Models;
+
+public static class ProjectTestData
+{
+ public static Project GetProject(int id)
+ => new Faker()
+ .CustomInstantiator(_ => new Project
+ {
+ Id = id,
+ Name = $"Project {id}",
+ OpenDate = DateTime.Now,
+ DueDate = DateTime.Now.AddDays(1),
+ })
+ .Generate();
+
+ public static List GetProjects(int total)
+ => Enumerable
+ .Range(1, total)
+ .Select(GetProject)
+ .ToList();
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Data/TaskTestData.cs b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Data/TaskTestData.cs
new file mode 100644
index 0000000..22c64ef
--- /dev/null
+++ b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/Data/TaskTestData.cs
@@ -0,0 +1,30 @@
+namespace AutoCrudAdmin.Demo.SqlServer.Tests.Data;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Bogus;
+using Models.Models;
+
+public static class TaskTestData
+{
+ public static Task GetTask(int id, int projectId)
+ => new Faker()
+ .CustomInstantiator(f => new Task
+ {
+ Id = id,
+ Name = $"Task {id}",
+ OpenDate = DateTime.Now,
+ DueDate = DateTime.Now.AddDays(1),
+ LabelType = f.PickRandom(),
+ ExecutionType = f.PickRandom(),
+ ProjectId = projectId,
+ })
+ .Generate();
+
+ public static List GetTasks(int total, int totalProjects)
+ => Enumerable
+ .Range(1, total)
+ .Select(i => GetTask(i, new Faker().Random.Number(1, totalProjects)))
+ .ToList();
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Demo.SqlServer.Tests/TestStartup.cs b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/TestStartup.cs
new file mode 100644
index 0000000..611f248
--- /dev/null
+++ b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/TestStartup.cs
@@ -0,0 +1,5 @@
+namespace AutoCrudAdmin.Demo.SqlServer.Tests;
+
+public class TestStartup : Startup
+{
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Demo.SqlServer.Tests/testsettings.json b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/testsettings.json
new file mode 100644
index 0000000..8593c62
--- /dev/null
+++ b/tests/AutoCrudAdmin.Demo.SqlServer.Tests/testsettings.json
@@ -0,0 +1,2 @@
+{
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/AutoCrudAdmin.Tests.csproj b/tests/AutoCrudAdmin.Tests/AutoCrudAdmin.Tests.csproj
new file mode 100644
index 0000000..3d6cc4b
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/AutoCrudAdmin.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net6.0
+ enable
+ SA1600
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/AutoCrudAdmin.Tests/Extensions/DbContextExtensionsTests.cs b/tests/AutoCrudAdmin.Tests/Extensions/DbContextExtensionsTests.cs
new file mode 100644
index 0000000..62fc316
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Extensions/DbContextExtensionsTests.cs
@@ -0,0 +1,26 @@
+namespace AutoCrudAdmin.Tests.Extensions;
+
+using System.Linq;
+using AutoCrudAdmin.Extensions;
+using Demo.Models.Models;
+using FluentAssertions;
+using Infrastructure;
+using Xunit;
+
+public class DbContextExtensionsTests : TestsWithData
+{
+ [Fact]
+ public void Set_ReturnsQueryableOfGivenType()
+ {
+ // Arrange
+ var entityType = typeof(Project);
+
+ // Act
+ var result = this.Data.Set(entityType);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().BeAssignableTo>();
+ result.ElementType.Should().Be(entityType);
+ }
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/Extensions/StringExtensionsTests.cs b/tests/AutoCrudAdmin.Tests/Extensions/StringExtensionsTests.cs
new file mode 100644
index 0000000..6948ef9
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Extensions/StringExtensionsTests.cs
@@ -0,0 +1,66 @@
+namespace AutoCrudAdmin.Tests.Extensions;
+
+using AutoCrudAdmin.Extensions;
+using FluentAssertions;
+using Xunit;
+
+public class StringExtensionsTests
+{
+ [Theory]
+ [InlineData("PascalCase", "pascal-case")]
+ [InlineData("camelCase", "camel-case")]
+ public void ToHyphenSeparatedWords_ReturnsExpected(string input, string expected)
+ {
+ // Act
+ var result = input.ToHyphenSeparatedWords();
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("PascalCase", "Pascal Case")]
+ public void ToSpaceSeparatedWords_ReturnsExpected(string input, string expected)
+ {
+ // Act
+ var result = input.ToSpaceSeparatedWords();
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("TestController", "Test")]
+ [InlineData("ProductsController", "Products")]
+ [InlineData("AdminController", "Admin")]
+ public void ToControllerBaseUri_RemovesControllerSuffix(string input, string expected)
+ {
+ // Act
+ var result = input.ToControllerBaseUri();
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("This is a very long string", 5)]
+ public void ToEllipsis_ShortensString(string input, int maxLength)
+ {
+ // Act
+ var result = input.ToEllipsis(maxLength);
+
+ // Assert
+ result.Should().Be("Thi...");
+ }
+
+ [Theory]
+ [InlineData("Short", 10)]
+ public void ToEllipsis_ReturnsOriginalIfShorter(string input, int maxLength)
+ {
+ // Act
+ var result = input.ToEllipsis(maxLength);
+
+ // Assert
+ result.Should().Be("Short");
+ }
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/Extensions/TypeExtensionsTests.cs b/tests/AutoCrudAdmin.Tests/Extensions/TypeExtensionsTests.cs
new file mode 100644
index 0000000..21db01a
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Extensions/TypeExtensionsTests.cs
@@ -0,0 +1,176 @@
+namespace AutoCrudAdmin.Tests.Extensions;
+
+using System.Collections.Generic;
+using AutoCrudAdmin.Extensions;
+using Demo.Models.Models;
+using FluentAssertions;
+using Infrastructure;
+using Xunit;
+
+public class TypeExtensionsTests : TestsWithData
+{
+ [Fact]
+ public void GetPrimaryKeyPropertyInfos_ReturnsExpected()
+ {
+ // Arrange
+ var type = typeof(Project);
+
+ // Act
+ var result = type.GetPrimaryKeyPropertyInfos();
+
+ // Assert
+ result.Should().Contain(p => p.Name == nameof(Project.Id));
+ }
+
+ [Theory]
+ [InlineData(1)]
+ public void GetPrimaryKeyValue_ReturnsExpected(int id)
+ {
+ // Arrange
+ var entity = new Project { Id = id };
+
+ // Act
+ var result = typeof(Project).GetPrimaryKeyValue(entity);
+
+ // Assert
+ result.Should().AllSatisfy(item =>
+ {
+ item.Key.Should().Be(Constants.Entity.SinglePrimaryKeyName);
+ item.Value.Should().Be(id);
+ });
+ }
+
+ [Fact]
+ public void IsSubclassOfRawGeneric_ShouldDetectsSubclass()
+ {
+ // Arrange
+ var subtype = typeof(TestSubEntity);
+ var baseType = typeof(TestBaseEntity<>);
+
+ // Act
+ var result = subtype.IsSubclassOfRawGeneric(baseType);
+
+ // Assert
+ result.Should().BeTrue();
+ }
+
+ [Fact]
+ public void IsSubclassOfRawGeneric_ShouldReturnFalseForSameType()
+ {
+ // Arrange
+ var type = typeof(TestBaseEntity<>);
+
+ // Act
+ var result = type.IsSubclassOfRawGeneric(type);
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void IsSubclassOfAnyType_DetectsSubclasses()
+ {
+ // Arrange
+ var subclass = typeof(TestSubEntity);
+ var baseClass = typeof(TestBaseEntity);
+ var genericBaseClass = typeof(TestBaseEntity<>);
+
+ // Act
+ var result1 = subclass.IsSubclassOfAnyType(baseClass);
+ var result2 = subclass.IsSubclassOfAnyType(genericBaseClass);
+
+ // Assert
+ result1.Should().BeTrue(); // Subclass of direct base class
+ result2.Should().BeTrue(); // Subclass of generic base class
+ }
+
+ [Fact]
+ public void IsSubclassOfAnyType_ReturnsFalseWhenNoRelation()
+ {
+ // Arrange
+ var type1 = typeof(TestBaseEntity);
+ var type2 = typeof(Project);
+
+ // Act
+ var result = type1.IsSubclassOfAnyType(type2);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsNavigationProperty_DetectsNavigationTypes()
+ {
+ // Arrange
+ var navigationType = typeof(ICollection);
+ var nonNavigationType = typeof(int);
+
+ // Act
+ var result1 = navigationType.IsNavigationProperty();
+ var result2 = nonNavigationType.IsNavigationProperty();
+
+ // Assert
+ result1.Should().BeTrue();
+ result2.Should().BeFalse();
+ }
+
+ [Fact]
+ public void IsNavigationProperty_HandlesString()
+ {
+ // Arrange
+
+ // Act
+ var result = typeof(string).IsNavigationProperty();
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void UnProxy_ReturnsSameTypeIfNotProxy()
+ {
+ // Arrange
+ var type = typeof(Project);
+
+ // Act
+ var result = type.UnProxy();
+
+ // Assert
+ result.Should().BeSameAs(type);
+ }
+
+ [Fact]
+ public void IsEnumerableExceptString_DetectsEnumerableTypes()
+ {
+ // Arrange
+ var enumerableType = typeof(List);
+
+ // Act
+ var result = enumerableType.IsEnumerableExceptString();
+
+ // Assert
+ result.Should().BeTrue();
+ }
+
+ [Fact]
+ public void IsEnumerableExceptString_ExcludesString()
+ {
+ // Act
+ var result = typeof(string).IsEnumerableExceptString();
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ private class TestBaseEntity
+ {
+ }
+
+ private class TestBaseEntity : TestBaseEntity
+ {
+ }
+
+ private class TestSubEntity : TestBaseEntity
+ {
+ }
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/Helpers/ExpressionsBuilderTests.cs b/tests/AutoCrudAdmin.Tests/Helpers/ExpressionsBuilderTests.cs
new file mode 100644
index 0000000..683c82d
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Helpers/ExpressionsBuilderTests.cs
@@ -0,0 +1,72 @@
+namespace AutoCrudAdmin.Tests.Helpers;
+
+using System;
+using System.Collections.Generic;
+using AutoCrudAdmin.Helpers;
+using Demo.Models.Models;
+using FluentAssertions;
+using Xunit;
+
+public class ExpressionsBuilderTests
+{
+ [Fact]
+ public void ForByEntityPrimaryKey_BuildsPredicate()
+ {
+ // Arrange
+ var dict = new Dictionary
+ {
+ [nameof(Task.Id)] = "1",
+ };
+
+ // Act
+ var result = ExpressionsBuilder.ForByEntityPrimaryKey(dict);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Type.Should().Be(typeof(Func));
+ }
+
+ [Fact]
+ public void ForGetProperty_BuildsPredicate()
+ {
+ // Arrange
+ var property = typeof(Task).GetProperty(nameof(Task.Name)) !;
+
+ // Act
+ var result = ExpressionsBuilder.ForGetProperty(property);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Type.Should().Be(typeof(Func));
+ }
+
+ [Fact]
+ public void ForCreateInstance_ReturnsFactoryMethod()
+ {
+ // Act
+ var result = ExpressionsBuilder.ForCreateInstance();
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().BeOfType>();
+ result().Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData(1)]
+ public void ForGetPropertyValue_ReturnsValueGetter(int id)
+ {
+ // Arrange
+ var property = typeof(Task).GetProperty(nameof(Task.Id)) !;
+
+ var entity = new Task { Id = id };
+
+ // Act
+ var result = ExpressionsBuilder.ForGetPropertyValue(property);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().BeOfType>();
+ result(entity).Should().Be(id);
+ }
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/Helpers/NavHelperTests.cs b/tests/AutoCrudAdmin.Tests/Helpers/NavHelperTests.cs
new file mode 100644
index 0000000..2befa38
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Helpers/NavHelperTests.cs
@@ -0,0 +1,36 @@
+namespace AutoCrudAdmin.Tests.Helpers;
+
+using System.Linq;
+using AutoCrudAdmin.Helpers;
+using Demo.SqlServer;
+using FluentAssertions;
+using Xunit;
+
+public class NavHelperTests
+{
+ [Fact]
+ public void GetNavItems_ReturnsDbSetPropertyNames()
+ {
+ // Arrange
+ var dbSetPropertiesNames = ReflectionHelper
+ .DbSetProperties
+ .Select(p => p.Name)
+ .ToList();
+
+ var expectedDbProperties = new[]
+ {
+ nameof(TaskSystemDbContext.Employees),
+ nameof(TaskSystemDbContext.EmployeeTasks),
+ nameof(TaskSystemDbContext.Projects),
+ nameof(TaskSystemDbContext.Tasks),
+ };
+
+ // Act
+ var result = NavHelper.GetNavItems().ToList();
+
+ // Assert
+ result.Should().NotBeEmpty();
+ result.Should().BeEquivalentTo(dbSetPropertiesNames);
+ result.Should().BeEquivalentTo(expectedDbProperties);
+ }
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/Helpers/ReflectionHelperTests.cs b/tests/AutoCrudAdmin.Tests/Helpers/ReflectionHelperTests.cs
new file mode 100644
index 0000000..a472b51
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Helpers/ReflectionHelperTests.cs
@@ -0,0 +1,38 @@
+namespace AutoCrudAdmin.Tests.Helpers;
+
+using System.Linq;
+using AutoCrudAdmin.Helpers;
+using Demo.SqlServer;
+using FluentAssertions;
+using Xunit;
+
+public class ReflectionHelperTests
+{
+ [Fact]
+ public void DbContexts_ShouldReturnAllDbContextTypes()
+ {
+ // Arrange
+ var dbContexts = ReflectionHelper.DbContexts;
+
+ // Assert
+ dbContexts.Should().ContainSingle(t => t == typeof(TaskSystemDbContext));
+ }
+
+ [Fact]
+ public void DbSetProperties_ShouldReturnAllDbSetProperties()
+ {
+ // Arrange
+ var dbSetProperties = ReflectionHelper.DbSetProperties;
+
+ var expectedDbProperties = new[]
+ {
+ nameof(TaskSystemDbContext.Tasks),
+ nameof(TaskSystemDbContext.Employees),
+ nameof(TaskSystemDbContext.Projects),
+ nameof(TaskSystemDbContext.EmployeeTasks),
+ };
+
+ // Assert
+ dbSetProperties.Select(p => p.Name).Should().BeEquivalentTo(expectedDbProperties);
+ }
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/Helpers/UrlsHelperTests.cs b/tests/AutoCrudAdmin.Tests/Helpers/UrlsHelperTests.cs
new file mode 100644
index 0000000..ca7531f
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Helpers/UrlsHelperTests.cs
@@ -0,0 +1,21 @@
+namespace AutoCrudAdmin.Tests.Helpers;
+
+using AutoCrudAdmin.Helpers;
+using FluentAssertions;
+using Xunit;
+
+public class UrlsHelperTests
+{
+ [Theory]
+ [InlineData("Column1", "Equals", "Column1-equals")]
+ [InlineData("ColumnWithCamelCase", "NotEquals", "ColumnWithCamelCase-not-equals")]
+ [InlineData("Column_With_Underscores", "Contains", "Column_With_Underscores-contains")]
+ public void GetQueryParamForColumnAndFilter_ReturnsExpected(string columnName, string filterName, string expected)
+ {
+ // Act
+ var result = UrlsHelper.GetQueryParamForColumnAndFilter(columnName, filterName);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+}
\ No newline at end of file
diff --git a/tests/AutoCrudAdmin.Tests/Infrastructure/TestsWithData.cs b/tests/AutoCrudAdmin.Tests/Infrastructure/TestsWithData.cs
new file mode 100644
index 0000000..728fc60
--- /dev/null
+++ b/tests/AutoCrudAdmin.Tests/Infrastructure/TestsWithData.cs
@@ -0,0 +1,19 @@
+namespace AutoCrudAdmin.Tests.Infrastructure;
+
+using System;
+using Demo.SqlServer;
+using Microsoft.EntityFrameworkCore;
+
+public abstract class TestsWithData
+{
+ protected TestsWithData()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(Guid.NewGuid().ToString())
+ .Options;
+
+ this.Data = new TaskSystemDbContext(options);
+ }
+
+ protected TaskSystemDbContext Data { get; }
+}
\ No newline at end of file