From cea79c2359eed52d26a68cac1a0f5d4d2bf9169f Mon Sep 17 00:00:00 2001 From: Andreas Mueller Date: Fri, 17 Dec 2021 00:14:38 +0100 Subject: [PATCH] Added HtmlHelper.IsDarkColor and enhanced UnitTests --- .../Utilities/HtmlHelper.cs | 55 ++++ .../Utilities/DelayedTaskTests.cs | 280 +++++++++++++++++- .../Utilities/DelayedTaskWithResultTests.cs | 134 +++++++++ CHANGELOG.md | 4 +- 4 files changed, 468 insertions(+), 5 deletions(-) create mode 100644 AMWD.Common.AspNetCore/Utilities/HtmlHelper.cs create mode 100644 AMWD.Common.Tests/Utilities/DelayedTaskWithResultTests.cs 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