Skip to content

Commit

Permalink
feat: add autoprop
Browse files Browse the repository at this point in the history
  • Loading branch information
jolexxa committed Oct 27, 2023
1 parent ed069e8 commit 14e0862
Show file tree
Hide file tree
Showing 12 changed files with 483 additions and 14 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,5 @@ dotnet_diagnostic.RCS1161.severity = none
dotnet_diagnostic.RCS1165.severity = none
# Allow keyword-based names so that parameter names like `@event` can be used.
dotnet_diagnostic.CA1716.severity = none
# Let me put comments where I like
dotnet_diagnostic.RCS1181.severity = none
10 changes: 6 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
{
"version": "0.2.0",
"configurations": [
// For these launch configurations to work, you need to setup a GODOT4
// For these launch configurations to work, you need to setup a GODOT
// environment variable. On mac or linux, this can be done by adding
// the following to your .zshrc, .bashrc, or .bash_profile file:
// export GODOT4="/Applications/Godot.app/Contents/MacOS/Godot"
// export GODOT="/Applications/Godot.app/Contents/MacOS/Godot"
{
"name": "🧪 Debug Tests",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${env:GODOT4}",
"program": "${env:GODOT}",
"args": [
// These command line flags are used by GoDotTest to run tests.
"--headless",
"--run-tests",
"--quit-on-finish"
],
Expand All @@ -24,9 +25,10 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${env:GODOT4}",
"program": "${env:GODOT}",
"args": [
// These command line flags are used by GoDotTest to run tests.
"--headless",
"--run-tests=${fileBasenameNoExtension}",
"--quit-on-finish"
],
Expand Down
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@
"**/LICENSE"
],
"omnisharp.enableEditorConfigSupport": true,
"omnisharp.enableImportCompletion": true,
"omnisharp.enableMsBuildLoadProjectsOnDemand": false,
"omnisharp.enableRoslynAnalyzers": true,
"omnisharp.maxFindSymbolsItems": 3000,
Expand All @@ -162,5 +161,6 @@
"icon": "terminal-powershell",
"source": "PowerShell"
}
}
},
"dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true
}
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
{
"label": "build-solutions",
"group": "test",
"command": "dotnet restore; ${env:GODOT4} --headless --build-solutions --quit || exit 0",
"command": "dotnet restore; ${env:GODOT} --headless --build-solutions --quit || exit 0",
"type": "shell",
"options": {
"cwd": "${workspaceFolder}/Chickensoft.GoDotCollections.Tests"
Expand Down
4 changes: 2 additions & 2 deletions Chickensoft.GoDotCollections.Tests/badges/branch_coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions Chickensoft.GoDotCollections.Tests/coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# To collect code coverage, you will need the following environment setup:
#
# - A "GODOT4" environment variable pointing to the Godot executable
# - A "GODOT" environment variable pointing to the Godot executable
# - ReportGenerator installed
#
# dotnet tool install -g dotnet-reportgenerator-globaltool
Expand All @@ -26,8 +26,8 @@ dotnet build --no-restore

coverlet \
"./.godot/mono/temp/bin/Debug" --verbosity detailed \
--target $GODOT4 \
--targetargs "--run-tests --coverage --quit-on-finish" \
--target $GODOT \
--targetargs "--headless --run-tests --coverage --quit-on-finish" \
--format "opencover" \
--output "./coverage/coverage.xml" \
--exclude-by-file "**/test/**/*.cs" \
Expand Down
206 changes: 206 additions & 0 deletions Chickensoft.GoDotCollections.Tests/test/src/AutoPropTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
namespace Chickensoft.GoDotCollections.Tests;

using System;
using System.Collections.Generic;
using Godot;
using GoDotCollections;
using GoDotTest;
using Shouldly;

public class NotifierTest : TestClass {
public NotifierTest(Node testScene) : base(testScene) { }

public static class Utils {
public static void ClearWeakReference(WeakReference weakReference) {
weakReference.Target = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}

public static WeakReference CreateWeakReference() => new(
new AutoProp<int>(1)
);

[Test]
public void Initializes() {
var subject = new AutoProp<int>(1);
subject.Value.ShouldBe(1);
}

[Test]
public void InitializesWithComparer() {
var subject = new AutoProp<int>(1, EqualityComparer<int>.Default);
subject.Value.ShouldBe(1);
subject.Comparer.ShouldBe(EqualityComparer<int>.Default);
}

[Test]
public void SyncCallsHandlerImmediatelyAndAllowsUnsubscribe() {
using var subject = new AutoProp<int>(1);

var changedCalled = 0;
var syncCalled = 0;

void onSync(int value) {
value.ShouldBe(1);
syncCalled++;
}

subject.Changed += (value) => changedCalled++;
subject.Sync += onSync;

changedCalled.ShouldBe(0);
syncCalled.ShouldBe(1);

subject.Sync -= onSync;
subject.OnNext(2);

changedCalled.ShouldBe(1);
}

[Test]
public void ClearsEventHandlers() {
using var subject = new AutoProp<int>(1);

var changedCalled = 0;
var syncCalled = 0;
var completedCalled = 0;
var errorCalled = 0;

subject.Changed += (value) => changedCalled++;
subject.Sync += (value) => syncCalled++;
subject.Completed += () => completedCalled++;
subject.Error += (exception) => errorCalled++;

subject.Clear();

subject.OnNext(2);
subject.OnCompleted();
subject.OnError(new InvalidOperationException());

changedCalled.ShouldBe(0);
syncCalled.ShouldBe(1);
completedCalled.ShouldBe(0);
errorCalled.ShouldBe(0);
}

[Test]
public void CompletesAndBlocksOtherActions() {
using var subject = new AutoProp<int>(1);

var changedCalled = 0;
var syncCalled = 0;
var completedCalled = 0;
var errorCalled = 0;

subject.Changed += (value) => changedCalled++;
subject.Sync += (value) => syncCalled++;
subject.Completed += () => completedCalled++;
subject.Error += (exception) => errorCalled++;

subject.OnCompleted();
subject.OnCompleted();
subject.OnNext(2);
subject.Value.ShouldBe(1);
subject.OnError(new InvalidOperationException());

changedCalled.ShouldBe(0);
syncCalled.ShouldBe(1);
completedCalled.ShouldBe(1);
errorCalled.ShouldBe(0);
}

[Test]
public void CallsErrorHandler() {
using var subject = new AutoProp<int>(1);

var errorCalled = 0;

void onError(Exception exception) {
exception.ShouldBeOfType<InvalidOperationException>();
errorCalled++;
}

subject.Error += onError;

subject.OnError(new InvalidOperationException());
errorCalled.ShouldBe(1);

subject.Error -= onError;

subject.OnError(new InvalidOperationException());
errorCalled.ShouldBe(1);
}

[Test]
public void DisposesCorrectly() {
var subject = new AutoProp<int>(1);

var changedCalled = 0;
var syncCalled = 0;
var completedCalled = 0;
var errorCalled = 0;

subject.Changed += (value) => changedCalled++;
subject.Sync += (value) => syncCalled++;
subject.Completed += () => completedCalled++;
subject.Error += (exception) => errorCalled++;

subject.Dispose();

subject.OnNext(2);
subject.OnCompleted();
subject.OnError(new InvalidOperationException());
subject.Dispose();

changedCalled.ShouldBe(0);
syncCalled.ShouldBe(1);
completedCalled.ShouldBe(0);
errorCalled.ShouldBe(0);
}

[Test]
public void Finalizes() {
// Weak reference has to be created and cleared from a static function
// or else the GC won't ever collect it :P
var subject = CreateWeakReference();
Utils.ClearWeakReference(subject);
}

[Test]
public void DoesNotCallHandlersIfValueHasNotChanged() {
var subject = new AutoProp<int>(1);

var changedCalled = 0;
var syncCalled = 0;

subject.Changed += (value) => changedCalled++;
subject.Sync += (value) => syncCalled++;

subject.OnNext(2);
subject.OnNext(2);

changedCalled.ShouldBe(1);
syncCalled.ShouldBe(2);
}

[Test]
public void DoesNotCallHandlerWhileInsideHandler() {
var subject = new AutoProp<int>(1);

var changes = new List<int>();
var syncs = new List<int>();

subject.Changed += (value) => {
changes.Add(value);
subject.OnNext(3);
};
subject.Sync += (value) => syncs.Add(value);

subject.OnNext(2);

changes.ShouldBe(new[] { 2, 3 });
syncs.ShouldBe(new[] { 1, 2, 3 });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<EnableDynamicLoading>true</EnableDynamicLoading>
<LangVersion>preview</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Nullable>enable</Nullable>
<CopyAllFiles>true</CopyAllFiles>
<RootNamespace>Chickensoft.GoDotCollections</RootNamespace>
Expand All @@ -12,7 +13,7 @@
<DebugType>portable</DebugType>

<Title>Chickensoft.GoDotCollections</Title>
<Version>1.3.4</Version>
<Version>1.4.0</Version>
<Description>Chickensoft's collections collection.</Description>
<Copyright>© 2023 Chickensoft</Copyright>
<Authors>Chickensoft</Authors>
Expand All @@ -37,5 +38,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="SauceControl.InheritDoc" Version="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
3 changes: 3 additions & 0 deletions Chickensoft.GoDotCollections/src/Assembly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Chickensoft.GoDotCollections.Tests")]
Loading

0 comments on commit 14e0862

Please sign in to comment.