diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e6ef563 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,57 @@ +# The name of the workflow. +# This is the name that's displayed for status +# badges (commonly embedded in README.md files). +name: tests + +# Trigger this workflow on a push, or pull request to +# the production branch, when either C# or project files changed +on: + push: + pull_request: + branches: [ main ] + paths-ignore: + - 'README.md' + +# Create an environment variable named DOTNET_VERSION +# and set it as "6.0.x" +env: + DOTNET_VERSION: '6.0.x' # The .NET SDK version to use + +# Defines a single job named "build-and-test" +jobs: + build-and-test: + + # When the workflow runs, this is the name that is logged + # This job will run three times, once for each "os" defined + name: build-and-test-${{matrix.os}} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + + # Each job run contains these five steps + steps: + # 1) Check out the source code so that the workflow can access it. + - uses: actions/checkout@v2 + + # 2) Set up the .NET CLI environment for the workflow to use. + # The .NET version is specified by the environment variable. + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + # 3) Restore the dependencies and tools of a project or solution. + - name: Install dependencies + working-directory: ./Source + run: dotnet restore + + # 4) Build a project or solution and all of its dependencies. + - name: Build + working-directory: ./Source + run: dotnet build --configuration Debug --no-restore + + # 5) Test a project or solution. + - name: Test + working-directory: ./Source + run: dotnet test --no-restore --verbosity normal diff --git a/Source/Morris.Reducible.Tests/Morris.Reducible.Tests.csproj b/Source/Morris.Reducible.Tests/Morris.Reducible.Tests.csproj new file mode 100644 index 0000000..a1de570 --- /dev/null +++ b/Source/Morris.Reducible.Tests/Morris.Reducible.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/Source/Morris.Reducible.Tests/ReducerTests.cs b/Source/Morris.Reducible.Tests/ReducerTests.cs new file mode 100644 index 0000000..6512dc3 --- /dev/null +++ b/Source/Morris.Reducible.Tests/ReducerTests.cs @@ -0,0 +1,90 @@ +using Xunit; + +namespace Morris.Reducible.Tests +{ + public class ReducerTests + { + private record CounterState(int Counter); + private record IncrementCounter(int Amount); + + + [Fact] + public void WhenSimpleReducerInvoked_ThenStateIsUpdated() + { + //Given + var initialState = new CounterState(0); + var action = new IncrementCounter(1); + var counterIncrementCounterReducer = Reducer + .Given() + .Then((state, delta) => (true, state with { Counter = state.Counter + delta.Amount})); + + //When + (bool changed, var counterState) = counterIncrementCounterReducer(initialState, action); + + //Then + Assert.True(changed); + Assert.Equal(new CounterState(1), counterState); + } + + [Fact] + public void WhenConditionalReducerInvokedWithPassingCondition_ThenStateIsUpdated() + { + //Given + var initialState = new CounterState(0); + var action = new IncrementCounter(1); + var counterIncrementCounterReducer = Reducer + .Given() + .When((state, delta) => state.Counter < 2) + .Then((state, delta) => state with { Counter = state.Counter + delta.Amount }); + + //When + (bool changed, var counterState) = counterIncrementCounterReducer(initialState, action); + + //Then + Assert.True(changed); + Assert.Equal(new CounterState(1), counterState); + } + + [Fact] + public void WhenConditionalReducerInvokedWithFailingCondition_ThenStateIsNotUpdated() + { + //Given + var initialState = new CounterState(0); + var action = new IncrementCounter(1); + var counterIncrementCounterReducer = Reducer + .Given() + .When((state, delta) => state.Counter > 2) + .Then((state, delta) => state with { Counter = state.Counter + delta.Amount }); + + //When + (bool changed, var counterState) = counterIncrementCounterReducer(initialState, action); + + //Then + Assert.False(changed); + Assert.Equal(new CounterState(0), counterState); + } + + [Fact] + public void WhenCombinedReducerInvoked_ThenStateIsUpdated() + { + //Given + var initialState = new CounterState(0); + var action = new IncrementCounter(1); + var counterIncrementCounterReducer1 = Reducer + .Given() + .Then((state, delta) => (true, state with { Counter = state.Counter + delta.Amount })); + + var counterIncrementCounterReducer2 = Reducer + .Given() + .Then((state, delta) => (true, state with { Counter = state.Counter + delta.Amount })); + var combinedReducer = Reducer.Combine(counterIncrementCounterReducer1, counterIncrementCounterReducer2); + + //When + (bool changed, var counterState) = combinedReducer(initialState, action); + + //Then + Assert.True(changed); + Assert.Equal(new CounterState(2), counterState); + } + } +} diff --git a/Source/Morris.Reducible.sln b/Source/Morris.Reducible.sln index befab0b..ccc2919 100644 --- a/Source/Morris.Reducible.sln +++ b/Source/Morris.Reducible.sln @@ -44,6 +44,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{047A19CC-B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolyMorphicReducers", "Tutorials\06-PolymorphicReducers\PolyMorphicReducers.csproj", "{7EFBF5C3-8A70-4E04-B5C1-75C3EA09A76D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Morris.Reducible.Tests", "Morris.Reducible.Tests\Morris.Reducible.Tests.csproj", "{5522941C-7016-4634-8477-41B2274D1DBB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -78,6 +80,10 @@ Global {7EFBF5C3-8A70-4E04-B5C1-75C3EA09A76D}.Debug|Any CPU.Build.0 = Debug|Any CPU {7EFBF5C3-8A70-4E04-B5C1-75C3EA09A76D}.Release|Any CPU.ActiveCfg = Release|Any CPU {7EFBF5C3-8A70-4E04-B5C1-75C3EA09A76D}.Release|Any CPU.Build.0 = Release|Any CPU + {5522941C-7016-4634-8477-41B2274D1DBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5522941C-7016-4634-8477-41B2274D1DBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5522941C-7016-4634-8477-41B2274D1DBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5522941C-7016-4634-8477-41B2274D1DBB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Source/Morris.Reducible/Reducer.Builder.cs b/Source/Morris.Reducible/Reducer.Builder.cs index 398a5c4..d2ed654 100644 --- a/Source/Morris.Reducible/Reducer.Builder.cs +++ b/Source/Morris.Reducible/Reducer.Builder.cs @@ -7,7 +7,7 @@ namespace Morris.Reducible; public static partial class Reducer { - public static Builder New() => new Builder(); + public static Builder New() => new(); public class Builder { diff --git a/Source/Morris.Reducible/Reducer.ConditionBuilder.cs b/Source/Morris.Reducible/Reducer.ConditionBuilder.cs index f3b600c..434654f 100644 --- a/Source/Morris.Reducible/Reducer.ConditionBuilder.cs +++ b/Source/Morris.Reducible/Reducer.ConditionBuilder.cs @@ -9,7 +9,8 @@ public class ConditionBuilder { internal ConditionBuilder() { } - public ResultMapper When(Func condition) => new ResultMapper(condition); + public ResultMapper When(Func condition) => + new ResultMapper(condition); public Func> Then(Func> reducer) { @@ -20,15 +21,11 @@ public Func> Then(Func WhenReducedBy( - Func subStateSelector, - Func> reducer) - => + Func subStateSelector, Func> reducer) => new ObjectResultMapper(subStateSelector, reducer); public ImmutableArrayResultMapper WhenReducedBy( - Func> subStateSelector, - Func> reducer) - => - new ImmutableArrayResultMapper(subStateSelector, reducer); + Func> subStateSelector, Func> reducer) => + new ImmutableArrayResultMapper(subStateSelector, reducer); } } \ No newline at end of file diff --git a/Source/Morris.Reducible/Reducer.Result.cs b/Source/Morris.Reducible/Reducer.Result.cs index ca25d4b..289d458 100644 --- a/Source/Morris.Reducible/Reducer.Result.cs +++ b/Source/Morris.Reducible/Reducer.Result.cs @@ -5,6 +5,6 @@ public static partial class Reducer public record struct Result(bool Changed, TState State) { public static implicit operator (bool changed, TState state)(Result result) => (result.Changed, result.State); - public static implicit operator Result((bool changed, TState value) value) => new Result(value.changed, value.value); + public static implicit operator Result((bool changed, TState state) tuple) => new(tuple.changed, tuple.state); } } \ No newline at end of file diff --git a/Source/Morris.Reducible/Reducer.cs b/Source/Morris.Reducible/Reducer.cs index 9f89e5a..2c9124e 100644 --- a/Source/Morris.Reducible/Reducer.cs +++ b/Source/Morris.Reducible/Reducer.cs @@ -6,9 +6,9 @@ namespace Morris.Reducible; public static partial class Reducer { - public static ConditionBuilder Given() => new ConditionBuilder(); + public static ConditionBuilder Given() => new(); - public static Builder CreateBuilder() => new Builder(); + public static Builder CreateBuilder() => new(); public static Func> Combine(params Func>[] reducers) {