diff --git a/AMWD.Common.AspNetCore/Utilities/HtmlHelper.cs b/AMWD.Common.AspNetCore/Utilities/HtmlHelper.cs
new file mode 100644
index 0000000..1909e1a
--- /dev/null
+++ b/AMWD.Common.AspNetCore/Utilities/HtmlHelper.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace AMWD.Common.AspNetCore.Utilities
+{
+ ///
+ /// Provides helpers for webpages.
+ ///
+ public static class HtmlHelper
+ {
+ ///
+ /// Checks whether a color is considered as dark.
+ ///
+ /// The color (hex or rgb - as defined in CSS)
+ /// true when the color is dark otherwise false.
+ public static bool IsDarkColor(string color)
+ {
+ if (string.IsNullOrWhiteSpace(color))
+ return false;
+
+ int r, g, b;
+
+ var rgbMatch = Regex.Match(color, @"^rgba?\(([0-9]+), ?([0-9]+), ?([0-9]+)");
+ var hexMatchFull = Regex.Match(color, @"^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$");
+ var hexMatchLite = Regex.Match(color, @"^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$");
+
+ if (rgbMatch.Success)
+ {
+ r = Convert.ToInt32(rgbMatch.Groups[1].Value);
+ g = Convert.ToInt32(rgbMatch.Groups[2].Value);
+ b = Convert.ToInt32(rgbMatch.Groups[3].Value);
+ }
+ else if (hexMatchFull.Success)
+ {
+ r = Convert.ToInt32(hexMatchFull.Groups[1].Value, 16);
+ g = Convert.ToInt32(hexMatchFull.Groups[2].Value, 16);
+ b = Convert.ToInt32(hexMatchFull.Groups[3].Value, 16);
+ }
+ else if (hexMatchLite.Success)
+ {
+ r = Convert.ToInt32(new string(hexMatchLite.Groups[1].Value.First(), 2), 16);
+ g = Convert.ToInt32(new string(hexMatchLite.Groups[2].Value.First(), 2), 16);
+ b = Convert.ToInt32(new string(hexMatchLite.Groups[3].Value.First(), 2), 16);
+ }
+ else
+ {
+ return false;
+ }
+
+ double luminance = (r * 0.299 + g * 0.587 + b * 0.114) / 255;
+ return luminance <= 0.5;
+ }
+ }
+}
diff --git a/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs b/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs
index 9cdce47..4bd8bbe 100644
--- a/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs
+++ b/AMWD.Common.Tests/Utilities/DelayedTaskTests.cs
@@ -1,8 +1,8 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using System.Diagnostics;
+using System.Threading;
using System.Threading.Tasks;
+using AMWD.Common.Utilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AMWD.Common.Tests.Utilities
@@ -11,6 +11,278 @@ namespace AMWD.Common.Tests.Utilities
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class DelayedTaskTests
{
- // TODO
+ [TestMethod]
+ public void ShouldCreateNewDelayedTaskNotStarting()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { executionCount++; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ var delayedTask = DelayedTask.Create(action, delay);
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.AreEqual(delay, delayedTask.Delay);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(0, executionCount);
+ }
+
+ [TestMethod]
+ public void ShouldCreateNewDelayedTaskStarting()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { executionCount++; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ var delayedTask = DelayedTask.Run(action, delay);
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.AreEqual(delay, delayedTask.Delay);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(1, executionCount);
+ }
+
+ [TestMethod]
+ public void ShouldRunOnceAfterReset()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { executionCount++; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ var delayedTask = DelayedTask.Create(action, delay);
+ delayedTask.Reset();
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.AreEqual(delay, delayedTask.Delay);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(1, executionCount);
+ }
+
+ [TestMethod]
+ public void ShouldCancelWaitingTask()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { executionCount++; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ var delayedTask = DelayedTask.Run(action, delay);
+ delayedTask.Cancel();
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.AreEqual(delay, delayedTask.Delay);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(0, executionCount);
+ }
+
+ [TestMethod]
+ public async Task ShouldResetRunningDelay()
+ {
+ // arrange
+ int executionCount = 0;
+ var sw = new Stopwatch();
+
+ var delay = TimeSpan.FromMilliseconds(200);
+ var action = () => { sw.Stop(); executionCount++; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ sw.Start();
+ var delayedTask = DelayedTask.Run(action, delay);
+ await Task.Delay(50);
+ delayedTask.Reset();
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.AreEqual(delay, delayedTask.Delay);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(1, executionCount);
+ // delta of 50ms as the precision below 50ms is really bad for System.Timer
+ Assert.AreEqual(250, sw.ElapsedMilliseconds, 50);
+ }
+
+ [TestMethod]
+ public async Task ShouldNotExecutedImmediateOnCreated()
+ {
+ // arrange
+ int executionCount = 0;
+ var sw = new Stopwatch();
+
+ var delay = TimeSpan.FromMilliseconds(200);
+ var action = () => { sw.Stop(); executionCount++; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ sw.Start();
+ var delayedTask = DelayedTask.Create(action, delay);
+ await Task.Delay(50);
+ bool isSuccess = delayedTask.ExecutePending();
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+ sw.Stop();
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.AreEqual(delay, delayedTask.Delay);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(0, executionCount);
+ // delta of 50ms as the precision below 50ms is really bad for System.Timer
+ Assert.AreEqual(1250, sw.ElapsedMilliseconds, 50);
+ Assert.IsFalse(isSuccess);
+ }
+
+ [TestMethod]
+ public async Task ShouldExecuteImmediateOnExecutePendingWhenRunning()
+ {
+ // arrange
+ int executionCount = 0;
+ var sw = new Stopwatch();
+
+ var delay = TimeSpan.FromMilliseconds(200);
+ var action = () => { sw.Stop(); executionCount++; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ sw.Start();
+ var delayedTask = DelayedTask.Run(action, delay);
+ await Task.Delay(50);
+ bool isSuccess = delayedTask.ExecutePending();
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.AreEqual(delay, delayedTask.Delay);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(1, executionCount);
+ // delta of 50ms as the precision below 50ms is really bad for System.Timer
+ Assert.AreEqual(50, sw.ElapsedMilliseconds, 50);
+ Assert.IsTrue(isSuccess);
+ }
+
+ [TestMethod]
+ public void ShouldReturnAnAwaiter()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { executionCount++; };
+ var delayedTask = DelayedTask.Create(action, delay);
+
+ // act
+ delayedTask.Reset();
+ var awaiter = delayedTask.GetAwaiter();
+ SpinWait.SpinUntil(() => awaiter.IsCompleted);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.IsNotNull(awaiter);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNull(delayedTask.Exception);
+ Assert.AreEqual(1, executionCount);
+ }
+
+ [TestMethod]
+ public void ShouldHaveAnExceptionSet()
+ {
+ // arrange
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { throw new Exception("TEST :D"); };
+
+ // act
+ var delayedTask = DelayedTask.Run(action, delay);
+
+ var awaiter = delayedTask.GetAwaiter();
+ SpinWait.SpinUntil(() => awaiter.IsCompleted);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNotNull(delayedTask.Exception);
+ }
+
+ [TestMethod]
+ public void ShouldUseExceptionHandler()
+ {
+ // arrange
+ string exceptionText = null;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { throw new Exception("TEST :D"); };
+ var exceptionHandler = (Exception ex) =>
+ {
+ exceptionText = ex.Message;
+ };
+
+ // act
+ var delayedTask = DelayedTask.Run(action, delay)
+ .WithExceptionHandler(exceptionHandler);
+
+ var awaiter = delayedTask.GetAwaiter();
+ SpinWait.SpinUntil(() => awaiter.IsCompleted);
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.IsNotNull(delayedTask.Exception);
+ Assert.AreEqual("TEST :D", exceptionText);
+ }
+
+ [TestMethod]
+ public async Task ShouldReturnNormalTask()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var action = () => { executionCount++; };
+ var delayedTask = DelayedTask.Create(action, delay);
+
+ // act
+ delayedTask.Reset();
+ var task = delayedTask.Task;
+ await task;
+
+ // assert
+ Assert.IsNotNull(delayedTask);
+ Assert.IsNotNull(task);
+ Assert.IsInstanceOfType(task, typeof(Task));
+ Assert.IsFalse(delayedTask.IsRunning);
+ Assert.IsFalse(delayedTask.IsWaitingToRun);
+ Assert.AreEqual(1, executionCount);
+ Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
+ }
}
}
diff --git a/AMWD.Common.Tests/Utilities/DelayedTaskWithResultTests.cs b/AMWD.Common.Tests/Utilities/DelayedTaskWithResultTests.cs
new file mode 100644
index 0000000..4e67d50
--- /dev/null
+++ b/AMWD.Common.Tests/Utilities/DelayedTaskWithResultTests.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Common.Utilities;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace AMWD.Common.Tests.Utilities
+{
+ [TestClass]
+ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public class DelayedTaskWithResultTests
+ {
+ [TestMethod]
+ public void ShouldCreateNewDelayedTaskNotStarting()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var function = () => { executionCount++; return new[] { 42, 21 }; };
+
+ // act
+ var cts = new CancellationTokenSource(delay.Add(TimeSpan.FromSeconds(1)));
+ var delayedTaskWithResult = DelayedTask.Create(function, delay);
+ SpinWait.SpinUntil(() => executionCount > 0 || cts.IsCancellationRequested);
+
+ // assert
+ Assert.IsNotNull(delayedTaskWithResult);
+ Assert.AreEqual(delay, delayedTaskWithResult.Delay);
+ Assert.IsFalse(delayedTaskWithResult.IsRunning);
+ Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
+ Assert.IsNull(delayedTaskWithResult.Exception);
+ Assert.AreEqual(0, executionCount);
+ }
+
+ [TestMethod]
+ public async Task ShouldCreateNewDelayedTaskStarting()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var function = () => { executionCount++; return new[] { 42, 21 }; };
+
+ // act
+ var delayedTaskWithResult = DelayedTask.Run(function, delay);
+ int[] result = await delayedTaskWithResult;
+
+ // assert
+ Assert.IsNotNull(delayedTaskWithResult);
+ Assert.AreEqual(delay, delayedTaskWithResult.Delay);
+ Assert.IsFalse(delayedTaskWithResult.IsRunning);
+ Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
+ Assert.IsNull(delayedTaskWithResult.Exception);
+ Assert.AreEqual(1, executionCount);
+ CollectionAssert.AreEqual(new[] { 42, 21 }, result);
+ }
+
+ [TestMethod]
+ public void ShouldHaveAnExceptionSet()
+ {
+ // arrange
+ var delay = TimeSpan.FromMilliseconds(100);
+#pragma warning disable CS0162 // Unreachable Code detected.
+ var function = () => { throw new Exception("TEST :D"); return new[] { 42, 21 }; };
+#pragma warning restore CS0162 // Unreachable Code detected
+
+ // act
+ var delayedTaskWithResult = DelayedTask.Run(function, delay);
+
+ var awaiter = delayedTaskWithResult.GetAwaiter();
+ SpinWait.SpinUntil(() => awaiter.IsCompleted);
+
+ // assert
+ Assert.IsNotNull(delayedTaskWithResult);
+ Assert.IsFalse(delayedTaskWithResult.IsRunning);
+ Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
+ Assert.IsNotNull(delayedTaskWithResult.Exception);
+ }
+
+ [TestMethod]
+ public void ShouldUseExceptionHandler()
+ {
+ // arrange
+ string exceptionText = null;
+ var delay = TimeSpan.FromMilliseconds(100);
+#pragma warning disable CS0162 // Unreachable Code detected.
+ var function = () => { throw new Exception("TEST :D"); return new[] { 42, 21 }; };
+#pragma warning restore CS0162 // Unreachable Code detected
+ var exceptionHandler = (Exception ex) =>
+ {
+ exceptionText = ex.Message;
+ };
+
+ // act
+ var delayedTaskWithResult = DelayedTask.Run(function, delay)
+ .WithExceptionHandler(exceptionHandler);
+
+ var awaiter = delayedTaskWithResult.GetAwaiter();
+ SpinWait.SpinUntil(() => awaiter.IsCompleted);
+
+ // assert
+ Assert.IsNotNull(delayedTaskWithResult);
+ Assert.IsFalse(delayedTaskWithResult.IsRunning);
+ Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
+ Assert.IsNotNull(delayedTaskWithResult.Exception);
+ Assert.AreEqual("TEST :D", exceptionText);
+ }
+
+ [TestMethod]
+ public async Task ShouldReturnNormalTask()
+ {
+ // arrange
+ int executionCount = 0;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var function = () => { executionCount++; return new[] { 42, 21 }; };
+ var delayedTaskWithResult = DelayedTask.Create(function, delay);
+
+ // act
+ delayedTaskWithResult.Reset();
+ var task = delayedTaskWithResult.Task;
+ int[] result = await task;
+
+ // assert
+ Assert.IsNotNull(delayedTaskWithResult);
+ Assert.IsNotNull(task);
+ Assert.IsInstanceOfType(task, typeof(Task));
+ Assert.IsFalse(delayedTaskWithResult.IsRunning);
+ Assert.IsFalse(delayedTaskWithResult.IsWaitingToRun);
+ Assert.AreEqual(1, executionCount);
+ Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
+ CollectionAssert.AreEqual(new int[] { 42, 21 }, result);
+ }
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ab047f..8d04221 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased](https://git.am-wd.de/AM.WD/common/compare/v1.1.0...master)
### Added
- CHANGELOG
+- `HtmlHelper.IsDarkColor` to classify a color as dark or light one (by luminance)
### Changed
- Unit-Tests enhanced
-- Updated `NetRevisionTask`
+- Unit-Tests excluded from code coverage calcualtion
+- Updated NuGet package `NetRevisionTask`
## [v1.1.0](https://git.am-wd.de/AM.WD/common/compare/v1.0.2...v1.1.0) - 2021-11-22