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)
{