diff --git a/src/Plugin.All.OCR.sln b/src/Plugin.All.OCR.sln index 1e77006..b757a49 100644 --- a/src/Plugin.All.OCR.sln +++ b/src/Plugin.All.OCR.sln @@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.OCR.Maui.Tests", "..\test\Plugin.OCR.Tests\Plugin.OCR.Maui.Tests.csproj", "{6DE9A37F-8E12-4884-BB7E-970C35AB0FD6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,6 +38,10 @@ Global {BC8D5E54-81F7-45F1-AC48-F269DD5C95B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC8D5E54-81F7-45F1-AC48-F269DD5C95B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC8D5E54-81F7-45F1-AC48-F269DD5C95B3}.Release|Any CPU.Build.0 = Release|Any CPU + {6DE9A37F-8E12-4884-BB7E-970C35AB0FD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DE9A37F-8E12-4884-BB7E-970C35AB0FD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DE9A37F-8E12-4884-BB7E-970C35AB0FD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DE9A37F-8E12-4884-BB7E-970C35AB0FD6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/Plugin.OCR.Tests/BaseTest.cs b/test/Plugin.OCR.Tests/BaseTest.cs new file mode 100644 index 0000000..880f99e --- /dev/null +++ b/test/Plugin.OCR.Tests/BaseTest.cs @@ -0,0 +1,78 @@ +using System.Globalization; +using Plugin.OCR.Maui.Tests.Mocks; + +namespace Plugin.OCR.Maui.Tests +{ + public abstract class BaseTest : IDisposable + { + readonly CultureInfo _defaultCulture, _defaultUiCulture; + + bool _isDisposed; + + protected enum TestDuration + { + Short = 2000, + Medium = 5000, + Long = 10000 + } + + protected BaseTest() + { + _defaultCulture = Thread.CurrentThread.CurrentCulture; + _defaultUiCulture = Thread.CurrentThread.CurrentUICulture; + + DispatcherProvider.SetCurrent(new MockDispatcherProvider()); + //DeviceDisplay.SetCurrent(null); + } + + ~BaseTest() => Dispose(false); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool isDisposing) + { + if (_isDisposed) + { + return; + } + + Thread.CurrentThread.CurrentCulture = _defaultCulture; + Thread.CurrentThread.CurrentUICulture = _defaultUiCulture; + + //DeviceDisplay.SetCurrent(null); + DispatcherProvider.SetCurrent(null); + + _isDisposed = true; + } + + protected static Task GetStreamFromImageSource(StreamImageSource imageSource, CancellationToken token) + => imageSource.Stream(token); + + protected static bool StreamEquals(Stream a, Stream b) + { + if (a == b) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + + for (var i = 0; i < a.Length; i++) + { + if (a.ReadByte() != b.ReadByte()) + { + return false; + } + } + + return true; + } + } +} diff --git a/test/Plugin.OCR.Tests/Mocks/MockDispatcherProvider.cs b/test/Plugin.OCR.Tests/Mocks/MockDispatcherProvider.cs new file mode 100644 index 0000000..7a96bf7 --- /dev/null +++ b/test/Plugin.OCR.Tests/Mocks/MockDispatcherProvider.cs @@ -0,0 +1,80 @@ +namespace Plugin.OCR.Maui.Tests.Mocks; + +// Inspired by https://github.com/dotnet/maui/blob/main/src/Core/tests/UnitTests/TestClasses/DispatcherStub.cs +sealed class MockDispatcherProvider : IDispatcherProvider, IDisposable +{ + static readonly DispatcherMock dispatcherMock = new(); + + readonly ThreadLocal dispatcherInstance = new(() => dispatcherMock); + + public IDispatcher GetForCurrentThread() => dispatcherInstance.Value ?? throw new InvalidOperationException(); + + void IDisposable.Dispose() => dispatcherInstance.Dispose(); + + sealed class DispatcherMock : IDispatcher + { + public DispatcherMock() => ManagedThreadId = Environment.CurrentManagedThreadId; + + public bool IsDispatchRequired => false; + + public int ManagedThreadId { get; } + + public IDispatcherTimer CreateTimer() + { + return new DispatcherTimerStub(this); + } + + public bool Dispatch(Action action) + { + action(); + + return true; + } + + public bool DispatchDelayed(TimeSpan delay, Action action) + { + return false; + } + } + + sealed class DispatcherTimerStub : IDispatcherTimer, IDisposable + { + readonly DispatcherMock dispatcher; + + Timer? timer; + + public DispatcherTimerStub(DispatcherMock dispatcher) + { + this.dispatcher = dispatcher; + } + + public TimeSpan Interval { get; set; } + + public bool IsRepeating { get; set; } + + public bool IsRunning => timer != null; + + public event EventHandler? Tick; + + public void Start() + { + timer = new Timer(OnTimeout, null, Interval, IsRepeating ? Interval : Timeout.InfiniteTimeSpan); + + void OnTimeout(object? state) + { + dispatcher.Dispatch(() => Tick?.Invoke(this, EventArgs.Empty)); + } + } + + public void Stop() + { + Dispose(); + } + + public void Dispose() + { + timer?.Dispose(); + timer = null; + } + } +} diff --git a/test/Plugin.OCR.Tests/Plugin.OCR.Maui.Tests.csproj b/test/Plugin.OCR.Tests/Plugin.OCR.Maui.Tests.csproj new file mode 100644 index 0000000..75168f6 --- /dev/null +++ b/test/Plugin.OCR.Tests/Plugin.OCR.Maui.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + true + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Plugin.OCR.Tests/Usings.cs b/test/Plugin.OCR.Tests/Usings.cs new file mode 100644 index 0000000..c802f44 --- /dev/null +++ b/test/Plugin.OCR.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit;