diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..446b951 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..74f048e --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,70 @@ +name: .NET + +on: + workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + name: Build and analyze + runs-on: windows-latest + env: + ProjectName: 'AStar.Dev.Functional.Extensions' + RepositoryName: 'astar-dev-functional-extensions' + steps: + - name: Set up JDK + uses: actions/setup-java@v4.4.0 + with: + java-version: 17 + distribution: 'zulu' + + - name: Checkout + uses: actions/checkout@v4.2.1 + with: + fetch-depth: 0 + + - name: Cache SonarCloud packages + uses: actions/cache@v4.2.3 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache SonarCloud scanner + id: cache-sonar-scanner + uses: actions/cache@v4.2.3 + with: + path: .\.sonar\scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + + - name: Install SonarCloud scanner + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + shell: powershell + run: | + New-Item -Path .\.sonar\scanner -ItemType Directory + dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner + + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + shell: powershell + run: | + dotnet tool install --global dotnet-coverage + .\.sonar\scanner\dotnet-sonarscanner begin /k:"astar-development_${{ env.RepositoryName }}" /o:"astar-development" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml /d:sonar.scanner.scanAll=false + dotnet build --configuration Release + dotnet-coverage collect 'dotnet test --filter "FullyQualifiedName!~Acceptance.Tests"' -f xml -o 'coverage.xml' + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + + - name: Pack NuGet package + if: github.ref == 'refs/heads/main' + run: dotnet pack .\src\${{ env.ProjectName }}\${{ env.ProjectName }}.csproj + + - name: Push to NuGet + if: github.ref == 'refs/heads/main' + run: dotnet nuget push "**\${{ env.ProjectName }}.*.nupkg" --api-key ${{secrets.nuget_api_key}} --skip-duplicate --source https://api.nuget.org/v3/index.json + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..2182218 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/projectSettingsUpdater.xml +/.idea.astar-dev-functional-extensions.iml +/modules.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.gitignore b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.gitignore new file mode 100644 index 0000000..de8bd97 --- /dev/null +++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/.idea.AStar.Dev.Functional.Extensions.iml +/projectSettingsUpdater.xml +/modules.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.name b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.name new file mode 100644 index 0000000..be440be --- /dev/null +++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/.name @@ -0,0 +1 @@ +AStar.Dev.Functional.Extensions \ No newline at end of file diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/indexLayout.xml b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/vcs.xml b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.AStar.Dev.Functional.Extensions/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/indexLayout.xml b/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AStar.Dev.Functional.Extensions.sln b/AStar.Dev.Functional.Extensions.sln new file mode 100644 index 0000000..2d11f8b --- /dev/null +++ b/AStar.Dev.Functional.Extensions.sln @@ -0,0 +1,47 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8456212E-F453-483A-8E27-7494722AE10F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3C168EA0-2CFB-490A-8E3A-BE08F456DD3F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Functional.Extensions", "src\AStar.Dev.Functional.Extensions\AStar.Dev.Functional.Extensions.csproj", "{3FC5E2DB-6C2C-4D72-8498-39A121722334}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStar.Dev.Functional.Extensions.Tests.Unit", "test\AStar.Dev.Functional.Extensions.Tests.Unit\AStar.Dev.Functional.Extensions.Tests.Unit.csproj", "{F61660A7-160E-4654-94A5-F8D5D75FFBD6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{419B9C55-6F41-4ED8-B87C-855B23E01C41}" + ProjectSection(SolutionItems) = preProject + .github\dependabot.yml = .github\dependabot.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{2BB3AD42-1BED-4792-8E37-E87783AE949C}" + ProjectSection(SolutionItems) = preProject + .github\workflows\dotnet.yml = .github\workflows\dotnet.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3FC5E2DB-6C2C-4D72-8498-39A121722334} = {8456212E-F453-483A-8E27-7494722AE10F} + {F61660A7-160E-4654-94A5-F8D5D75FFBD6} = {3C168EA0-2CFB-490A-8E3A-BE08F456DD3F} + {2BB3AD42-1BED-4792-8E37-E87783AE949C} = {419B9C55-6F41-4ED8-B87C-855B23E01C41} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3FC5E2DB-6C2C-4D72-8498-39A121722334}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FC5E2DB-6C2C-4D72-8498-39A121722334}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FC5E2DB-6C2C-4D72-8498-39A121722334}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FC5E2DB-6C2C-4D72-8498-39A121722334}.Release|Any CPU.Build.0 = Release|Any CPU + {F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F61660A7-160E-4654-94A5-F8D5D75FFBD6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/AStar.ico b/AStar.ico new file mode 100644 index 0000000..0b9aea4 Binary files /dev/null and b/AStar.ico differ diff --git a/AStar.png b/AStar.png new file mode 100644 index 0000000..8cac0a1 Binary files /dev/null and b/AStar.png differ diff --git a/build-and-test.ps1 b/build-and-test.ps1 new file mode 100644 index 0000000..aad68a7 --- /dev/null +++ b/build-and-test.ps1 @@ -0,0 +1,3 @@ +dotnet build +dotnet test +dotnet stryker --open-report --break-at 99 \ No newline at end of file diff --git a/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.csproj b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.csproj new file mode 100644 index 0000000..1ea161b --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.csproj @@ -0,0 +1,51 @@ + + + + latest-recommended + AStar Developement, Jason Barden + AStar Development + AStar Developement, 2025 + AStar.Dev.Functional.Extensions package contains some basic implementations of Option, Some and None. This list may expand over time. + $(AssemblyName).xml + True + True + true + enable + True + true + enable + AStar.png + LICENSE + https://github.com/astar-development/astar-dev-functional-extensions/ + Readme.md + Update. + True + git + https://github.com/astar-development/astar-dev-functional-extensions.git + snupkg + net9.0 + AStar.Dev.Functional.Extensions + 0.1.0 + + + + + + + + + + + + + + True + 1701;1702; + + + + True + 1701;1702; + + + diff --git a/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.xml b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.xml new file mode 100644 index 0000000..034a31a --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/AStar.Dev.Functional.Extensions.xml @@ -0,0 +1,148 @@ + + + + AStar.Dev.Functional.Extensions + + + + + + + + + + + + + + + + + The class that exists for when we have no object + + + + + The Value will always return an instance of , it can never return an actual value + + + + + The Of{T} method can be used to return an instance of the generic when there is no actual value to return + + The type of the original object that this instance of is replacing + The appropriate type of + + + + The class replaces the object when there is no object available + + The type of the original object + + + + The ToString method is overridden to always return "None" + + "None" no matter what the original type was + + + + The class that contains the original object + + + + + The Optional method will convert the 'raw' object to an + + The type of the original object + The object to return as an option + The original object as an option (and implemented as an instance of + + + + The class contains a basic set of extension methods to help map, filter, etc. the original object + + + + + The Map{T,TResult} method will either map the Some{T} to a new Some or return a new None of the specified result type + + The type of the source object + The type of object expected from the map + The object to map + The Map function + Either a new Some or a new None of the specified type + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The generic class contains the result for a successful method + + The type of the return object + + + The content (actual result) object + + + + The generic class contains the result for a successful method + + The type of the return object + + + The content (actual result) object + + + + The content (actual result) object + + + + + Overrides the default ToString to return the object type or <null> + + + Once the ToJson method (in AStar.Dev.Utilities) respects the 'Mask', 'Ignore' etc. attributes, we can reconsider whether this method returns the actual object + + The object type name or null. + + + diff --git a/src/AStar.Dev.Functional.Extensions/AStar.ico b/src/AStar.Dev.Functional.Extensions/AStar.ico new file mode 100644 index 0000000..0b9aea4 Binary files /dev/null and b/src/AStar.Dev.Functional.Extensions/AStar.ico differ diff --git a/src/AStar.Dev.Functional.Extensions/AStar.png b/src/AStar.Dev.Functional.Extensions/AStar.png new file mode 100644 index 0000000..8cac0a1 Binary files /dev/null and b/src/AStar.Dev.Functional.Extensions/AStar.png differ diff --git a/src/AStar.Dev.Functional.Extensions/EnumerableExtensions.cs b/src/AStar.Dev.Functional.Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..9c97003 --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/EnumerableExtensions.cs @@ -0,0 +1,20 @@ +namespace AStar.Dev.Functional.Extensions; + +/// +/// +public static class EnumerableExtensions +{ + /// + /// + /// + /// + /// + /// + public static Option FirstOrNone(this IEnumerable sequence, Func predicate) + { + return sequence.Where(predicate) + .Select>(x => x) + .DefaultIfEmpty(None.Value) + .First(); + } +} diff --git a/src/AStar.Dev.Functional.Extensions/LICENSE b/src/AStar.Dev.Functional.Extensions/LICENSE new file mode 100644 index 0000000..0b1b024 --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 AStar Development, Jason Barden + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/AStar.Dev.Functional.Extensions/None.cs b/src/AStar.Dev.Functional.Extensions/None.cs new file mode 100644 index 0000000..ae933a4 --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/None.cs @@ -0,0 +1,22 @@ +namespace AStar.Dev.Functional.Extensions; + +/// +/// The class that exists for when we have no object +/// +public sealed class None +{ + /// + /// The Value will always return an instance of , it can never return an actual value + /// + public static None Value { get; } = new(); + + /// + /// The Of{T} method can be used to return an instance of the generic when there is no actual value to return + /// + /// The type of the original object that this instance of is replacing + /// The appropriate type of + public static None Of() + { + return new(); + } +} diff --git a/src/AStar.Dev.Functional.Extensions/None{T}.cs b/src/AStar.Dev.Functional.Extensions/None{T}.cs new file mode 100644 index 0000000..120e54d --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/None{T}.cs @@ -0,0 +1,17 @@ +namespace AStar.Dev.Functional.Extensions; + +/// +/// The class replaces the object when there is no object available +/// +/// The type of the original object +public sealed class None : Option +{ + /// + /// The ToString method is overridden to always return "None" + /// + /// "None" no matter what the original type was + public override string ToString() + { + return "None"; + } +} diff --git a/src/AStar.Dev.Functional.Extensions/Option.cs b/src/AStar.Dev.Functional.Extensions/Option.cs new file mode 100644 index 0000000..b3cbb4b --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/Option.cs @@ -0,0 +1,20 @@ +namespace AStar.Dev.Functional.Extensions; + +/// +/// The class that contains the original object +/// +#pragma warning disable CA1716 // Rename type Option so that it no longer conflicts with the reserved language keyword 'Option' +public static class Option +#pragma warning restore CA1716 +{ + /// + /// The Optional method will convert the 'raw' object to an + /// + /// The type of the original object + /// The object to return as an option + /// The original object as an option (and implemented as an instance of + public static Option Optional(this T obj) + { + return new Some(obj); + } +} diff --git a/src/AStar.Dev.Functional.Extensions/OptionExtensions.cs b/src/AStar.Dev.Functional.Extensions/OptionExtensions.cs new file mode 100644 index 0000000..5c3b401 --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/OptionExtensions.cs @@ -0,0 +1,53 @@ +namespace AStar.Dev.Functional.Extensions; + +/// +/// The class contains a basic set of extension methods to help map, filter, etc. the original object +/// +public static class OptionExtensions +{ + /// + /// The Map{T,TResult} method will either map the Some{T} to a new Some or return a new None of the specified result type + /// + /// The type of the source object + /// The type of object expected from the map + /// The object to map + /// The Map function + /// Either a new Some or a new None of the specified type + public static Option Map(this Option obj, Func map) + { + return obj is Some some ? new Some(map(some.Content)) : new None(); + } + + /// + /// + /// + /// + /// + /// + public static Option Filter(this Option obj, Func predicate) + { + return obj is Some some && !predicate(some.Content) ? new None() : obj; + } + + /// + /// + /// + /// + /// + /// + public static T Reduce(this Option obj, T substitute) + { + return obj is Some some ? some.Content : substitute; + } + + /// + /// + /// + /// + /// + /// + public static T Reduce(this Option obj, Func substitute) + { + return obj is Some some ? some.Content : substitute(); + } +} diff --git a/src/AStar.Dev.Functional.Extensions/Option{T}.cs b/src/AStar.Dev.Functional.Extensions/Option{T}.cs new file mode 100644 index 0000000..794828d --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/Option{T}.cs @@ -0,0 +1,25 @@ +namespace AStar.Dev.Functional.Extensions; + +/// +/// +/// +#pragma warning disable CA1716 // Rename type Option so that it no longer conflicts with the reserved language keyword 'Option' +public abstract class Option +#pragma warning restore CA1716 +{ + /// + /// + /// + public static implicit operator Option(None _) + { + return new None(); + } + + /// + /// + /// + public static implicit operator Option(T value) + { + return new Some(value); + } +} diff --git a/src/AStar.Dev.Functional.Extensions/Readme.md b/src/AStar.Dev.Functional.Extensions/Readme.md new file mode 100644 index 0000000..16dacd1 --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/Readme.md @@ -0,0 +1 @@ +Update \ No newline at end of file diff --git a/src/AStar.Dev.Functional.Extensions/Some.cs b/src/AStar.Dev.Functional.Extensions/Some.cs new file mode 100644 index 0000000..53540f6 --- /dev/null +++ b/src/AStar.Dev.Functional.Extensions/Some.cs @@ -0,0 +1,28 @@ +namespace AStar.Dev.Functional.Extensions; + +/// +/// The generic class contains the result for a successful method +/// +/// The type of the return object +/// +/// +/// The content (actual result) object +public sealed class Some(T content) : Option +{ + /// + /// The content (actual result) object + /// + public T Content { get; } = content; + + /// + /// Overrides the default ToString to return the object type or <null> + /// + /// + /// Once the ToJson method (in AStar.Dev.Utilities) respects the 'Mask', 'Ignore' etc. attributes, we can reconsider whether this method returns the actual object + /// + /// The object type name or null. + public override string ToString() + { + return Content?.ToString() ?? ""; + } +} diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/AStar.Dev.Functional.Extensions.Tests.Unit.csproj b/test/AStar.Dev.Functional.Extensions.Tests.Unit/AStar.Dev.Functional.Extensions.Tests.Unit.csproj new file mode 100644 index 0000000..5556a7d --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/AStar.Dev.Functional.Extensions.Tests.Unit.csproj @@ -0,0 +1,51 @@ + + + + net9.0 + enable + enable + false + AStar.Dev.Functional.Extensions + True + latest-recommended + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + True + 1701;1702;IDE0058; + + + + True + 1701;1702;IDE0058; + + + diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/EnumerableExtensionsShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/EnumerableExtensionsShould.cs new file mode 100644 index 0000000..162c82b --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/EnumerableExtensionsShould.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; + +namespace AStar.Dev.Functional.Extensions; + +[TestSubject(typeof(EnumerableExtensions))] +public class EnumerableExtensionsShould +{ + [Fact] + public void ReturnTheExpectedFirstObject() + { + var list = new List { 1, 2, 3 }; + + list.FirstOrNone(i => i == 2).ShouldBeAssignableTo>(); + } + + [Fact] + public void ReturnTheExpectedNoneObject() + { + var list = new List { 1, 2, 3 }; + + list.FirstOrNone(i => i > 3).ShouldBeAssignableTo>(); + } +} diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/GenericNoneShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/GenericNoneShould.cs new file mode 100644 index 0000000..484b63c --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/GenericNoneShould.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; + +namespace AStar.Dev.Functional.Extensions; + +[TestSubject(typeof(None<>))] +public class GenericNoneShould +{ + [Fact] + public void OverrideToStringAsExpected() + { + None.Of().ToString().ShouldBe("None"); + } +} diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/MockClass.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/MockClass.cs new file mode 100644 index 0000000..0ed5353 --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/MockClass.cs @@ -0,0 +1,16 @@ +namespace AStar.Dev.Functional.Extensions; + +internal sealed class MockClass +{ + public string SomeText { get; set; } = "SomeText"; + + public static MockClass Create() + { + return new() { SomeText = "MockClassCreate" }; + } +} + +internal sealed class MockClass2 +{ + public string SomeText { get; set; } = "SomeText"; +} diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/NoneShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/NoneShould.cs new file mode 100644 index 0000000..0e97bd1 --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/NoneShould.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; + +namespace AStar.Dev.Functional.Extensions; + +[TestSubject(typeof(None))] +public class NoneShould +{ + [Fact] + public void ReturnNone() + { + None.Value.ShouldBeAssignableTo(); + } + + [Fact] + public void ReturnNoneOf() + { + None.Of().ShouldBeAssignableTo>(); + } +} diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionExtensionsShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionExtensionsShould.cs new file mode 100644 index 0000000..fd0a3a9 --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionExtensionsShould.cs @@ -0,0 +1,87 @@ +using JetBrains.Annotations; + +namespace AStar.Dev.Functional.Extensions; + +[TestSubject(typeof(OptionExtensions))] +public class OptionExtensionsShould +{ + [Fact] + public void MapTheSomeObjectToTheNewSomeObject() + { + var obj = new Some(new ()); + + var result = obj.Map(mockClass => new MockClass2 { SomeText = mockClass.SomeText }); + + result.ShouldBeAssignableTo>(); + } + + [Fact] + public void MapTheNoneObjectToTheNewNone() + { + var obj = new None(); + + var result = obj.Map(mockClass => new MockClass2 { SomeText = mockClass.SomeText }); + + result.ShouldBeAssignableTo>(); + } + + [Fact] + public void FilterTheSomeOptionBasedOnThePredicate() + { + var list = new List { 1, 2, 3, 4, 5 }.Optional(); + + var result = list.Filter(item => item.Contains(1)); + + result.ShouldBeAssignableTo>>(); + } + + [Fact] + public void FilterTheSomeOptionBasedOnThePredicateToNone() + { + var list = new List { 1, 2, 3, 4, 5 }.Optional(); + + var result = list.Filter(item => item.Contains(1234)); + + result.ShouldBeAssignableTo>>(); + } + + [Fact] + public void ReduceTheSomeContentToContainingType() + { + var obj = new Some(new () { SomeText = "SomeText" }); + + var result = obj.Reduce(new MockClass { SomeText = "SubstituteSomeText" }); + + result.SomeText.ShouldBe("SomeText"); + } + + [Fact] + public void ReduceTheSomeContentToContainingTypeUsingTheSubstituteObject() + { + var obj = new None(); + + var result = obj.Reduce(new MockClass { SomeText = "SubstituteSomeText" }); + + result.SomeText.ShouldBe("SubstituteSomeText"); + } + + [Fact] + public void ReduceTheSomeContentToTheContainingType() + { + var obj = new Some(new () { SomeText = "SomeText" }); + + var result = obj.Reduce(MockClass.Create); + + result.SomeText.ShouldBe("SomeText"); + } + + [Fact] + public void ReduceTheSomeContentToContainingTypeUsingTheSubstituteFunction() + { + var obj = new None(); + + var result = obj.Reduce(MockClass.Create); + + result.SomeText.ShouldBe("MockClassCreate"); + } +} diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionShould.cs new file mode 100644 index 0000000..5d79499 --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/OptionShould.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; + +namespace AStar.Dev.Functional.Extensions; + +[TestSubject(typeof(Option))] +public class OptionShould +{ + [Fact] + public void ReturnTheSpecifiedObjectAsAnOptionalObject() + { + new MockClass().Optional().ShouldBeAssignableTo>(); + } + + [Fact] + public void ReturnTheSpecifiedObjectAsAnInstanceOfTheSomeClass() + { + new MockClass().Optional().ShouldBeAssignableTo>(); + } +} diff --git a/test/AStar.Dev.Functional.Extensions.Tests.Unit/SomeShould.cs b/test/AStar.Dev.Functional.Extensions.Tests.Unit/SomeShould.cs new file mode 100644 index 0000000..435edb8 --- /dev/null +++ b/test/AStar.Dev.Functional.Extensions.Tests.Unit/SomeShould.cs @@ -0,0 +1,39 @@ +using JetBrains.Annotations; + +namespace AStar.Dev.Functional.Extensions; + +[TestSubject(typeof(Some<>))] +public class SomeShould +{ + [Fact] + public void ReturnTheExpectedStringContent() + { + var sut = new Some("Test"); + + sut.Content.ShouldBe("Test"); + } + + [Fact] + public void ReturnTheExpectedMockClassContent() + { + var sut = new Some(new ()); + + sut.Content.ShouldBeEquivalentTo(new MockClass()); + } + + [Fact] + public void ReturnTheExpectedToStringForTheObjectType() + { + var sut = new Some(new ()); + + sut.ToString().ShouldBe("AStar.Dev.Functional.Extensions.MockClass"); + } + + [Fact] + public void ReturnTheExpectedToStringForTheNull() + { + var sut = new Some(null!); + + sut.ToString().ShouldBe(""); + } +}