diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 0fe1570677..86483386e5 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -29,8 +29,9 @@ The repository uses multiple GitHub Actions workflows. What runs and when: 1. Each OS checks out code, restores, and builds locally 2. **Performance optimizations**: - Disables Windows Defender on Windows runners (significant speedup) -3. Runs two test jobs: - - **Non-parallel UnitTests**: `Tests/UnitTests` with diagnostic output +3. Runs three test jobs: + - **Non-parallel UnitTests.Legacy**: `Tests/UnitTests.Legacy` with diagnostic output + - **Non-parallel UnitTests.NonParallelizable**: `Tests/UnitTests.NonParallelizable` with diagnostic output - **Parallel UnitTestsParallelizable**: `Tests/UnitTestsParallelizable` with diagnostic output 4. Uploads test logs and diagnostic data from all runners @@ -87,7 +88,8 @@ The repository uses multiple GitHub Actions workflows. What runs and when: # Full CI sequence: dotnet restore dotnet build --configuration Debug --no-restore -dotnet test --project Tests/UnitTests --no-build --verbosity normal +dotnet test --project Tests/UnitTests.Legacy --no-build --verbosity normal +dotnet test --project Tests/UnitTests.NonParallelizable --no-build --verbosity normal dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal dotnet build --configuration Release --no-restore ``` diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 9cd63dfb0c..ee36a3263c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -47,14 +47,14 @@ jobs: Add-MpPreference -ExclusionPath "${{ github.workspace }}" Add-MpPreference -ExclusionProcess "dotnet.exe" - - name: Run UnitTests + - name: Run UnitTests.NonParallelizable shell: bash run: | dotnet test \ - --project Tests/UnitTests \ + --project Tests/UnitTests.NonParallelizable \ --no-build \ --verbosity normal \ - --diagnostic --diagnostic-output-directory logs/UnitTests/${{ runner.os }} + --diagnostic --diagnostic-output-directory logs/UnitTests.NonParallelizable/${{ runner.os }} - name: Upload Test Logs if: always() @@ -62,8 +62,8 @@ jobs: with: name: non_parallel_unittests-logs-${{ runner.os }} path: | - logs/UnitTests/ - Tests/UnitTests/logs/ + logs/UnitTests.NonParallelizable/ + Tests/UnitTests.NonParallelizable/logs/ TestResults/ **/*.dmp if-no-files-found: ignore diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 544579f229..1ae0543353 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -83,6 +83,8 @@ + + diff --git a/Terminal.sln b/Terminal.sln index e2663bcd20..30911810be 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -105,7 +105,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAot", "Examples\Nativ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Tests\Benchmarks\Benchmarks.csproj", "{242FBD3E-2EC6-4274-BD40-8E62AF9327B2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "Tests\UnitTests\UnitTests.csproj", "{038B09F5-EF3A-F21E-7C10-A6551866ECE2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Legacy", "Tests\UnitTests.Legacy\UnitTests.Legacy.csproj", "{038B09F5-EF3A-F21E-7C10-A6551866ECE2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.NonParallelizable", "Tests\UnitTests.NonParallelizable\UnitTests.NonParallelizable.csproj", "{B7C94F2A-3E8D-4A1B-9F5C-2D1E6A3B7C90}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "Tests\IntegrationTests\IntegrationTests.csproj", "{F74EC349-B988-FCFA-A1E5-967F70FB75B5}" EndProject @@ -123,7 +125,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{1A3C ProjectSection(SolutionItems) = preProject codecov.yml = codecov.yml Scripts\Run-LocalCoverage.ps1 = Scripts\Run-LocalCoverage.ps1 - Tests\UnitTests\runsettings.xml = Tests\UnitTests\runsettings.xml + Tests\UnitTests.Legacy\runsettings.xml = Tests\UnitTests.Legacy\runsettings.xml EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentExample", "Examples\FluentExample\FluentExample.csproj", "{8C05292F-86C9-C29A-635B-A4DFC5955D1C}" @@ -189,6 +191,10 @@ Global {038B09F5-EF3A-F21E-7C10-A6551866ECE2}.Debug|Any CPU.Build.0 = Debug|Any CPU {038B09F5-EF3A-F21E-7C10-A6551866ECE2}.Release|Any CPU.ActiveCfg = Release|Any CPU {038B09F5-EF3A-F21E-7C10-A6551866ECE2}.Release|Any CPU.Build.0 = Release|Any CPU + {B7C94F2A-3E8D-4A1B-9F5C-2D1E6A3B7C90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7C94F2A-3E8D-4A1B-9F5C-2D1E6A3B7C90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7C94F2A-3E8D-4A1B-9F5C-2D1E6A3B7C90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7C94F2A-3E8D-4A1B-9F5C-2D1E6A3B7C90}.Release|Any CPU.Build.0 = Release|Any CPU {F74EC349-B988-FCFA-A1E5-967F70FB75B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F74EC349-B988-FCFA-A1E5-967F70FB75B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {F74EC349-B988-FCFA-A1E5-967F70FB75B5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -248,6 +254,7 @@ Global {E6D716C6-AC94-4150-B10A-44AE13F79344} = {3DD033C0-E023-47BF-A808-9CCE30873C3E} {242FBD3E-2EC6-4274-BD40-8E62AF9327B2} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46} {038B09F5-EF3A-F21E-7C10-A6551866ECE2} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46} + {B7C94F2A-3E8D-4A1B-9F5C-2D1E6A3B7C90} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46} {F74EC349-B988-FCFA-A1E5-967F70FB75B5} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46} {96ACE8BA-2E07-7537-FBF2-E8176CCB8080} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46} {DE780834-190A-8277-51FD-750CC666E82D} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46} diff --git a/Tests/IntegrationTests/IntegrationTests.csproj b/Tests/IntegrationTests/IntegrationTests.csproj index 8b1545686a..1faeffd3ec 100644 --- a/Tests/IntegrationTests/IntegrationTests.csproj +++ b/Tests/IntegrationTests/IntegrationTests.csproj @@ -34,7 +34,7 @@ - + diff --git a/Tests/README.md b/Tests/README.md index 805a8b54a9..78efbc3b35 100644 --- a/Tests/README.md +++ b/Tests/README.md @@ -2,28 +2,90 @@ This folder contains the tests for Terminal.Gui. -## ./UnitTests +## Test Projects -This folder contains the unit tests for Terminal.Gui that can not be run in parallel. This is because they -depend on `Application` or other class that use static state or `ConfigurationManager`. +### ./UnitTestsParallelizable -We should be striving to move as many tests as possible to the `./UnitTestsParallelizable` folder. +The primary home for new tests. Tests here run in parallel (`parallelizeAssembly: true`, `maxParallelThreads: 12`) +and **must not** touch any process-wide static state. -## ./UnitTestsParallelizable +### ./UnitTests.NonParallelizable -This folder contains the unit tests for Terminal.Gui that can be run in parallel. +Tests that either explicitly test process-wide static state, or must set process-wide statics, or otherwise +cannot be run concurrently with other tests. These tests run with `parallelizeAssembly: false`. -## ./IntegrationTests +### ./UnitTests.Legacy -This folder contains the integration tests for Terminal.Gui. +Tests that have not yet been ported to `UnitTestsParallelizable` or `UnitTests.NonParallelizable`. +These tests are candidates for rewrite (if the case is not covered elsewhere) or deletion (if already covered). +Do not add new tests here. -## ./StressTests +### ./IntegrationTests -This folder contains the stress tests for Terminal.Gui. +Integration tests for Terminal.Gui. -## ./PerformanceTests +### ./StressTests -This folder WILL contain the performance tests for Terminal.Gui. +Stress tests for Terminal.Gui. +--- + +## Static State in Terminal.Gui + +The following classes and properties use process-wide static state. Tests that read or write these **must not** +go in `UnitTestsParallelizable` — they belong in `UnitTests.NonParallelizable`. + +### `Application` (legacy static façade — `Terminal.Gui.App/Legacy/`) + +| Member | Notes | +|--------|-------| +| `Application.Instance` | Returns `ApplicationImpl.Instance` (singleton) | +| `Application.Init(driverName)` | Initializes the singleton; sets `Initialized`, `Driver`, `MainThreadId`, `SynchronizationContext` | +| `Application.Shutdown()` | Disposes the singleton; resets all static fields | +| `Application.Initialized` | Global bool; true between `Init` and `Shutdown` | +| `Application.MainThreadId` | Process-wide main thread ID set by `Init` | +| `Application.Driver` | The active `IDriver` singleton | +| `Application.DefaultKeyBindings` | Process-wide default key-binding dictionary (mutable `IDictionary`) | +| `Application.ForceDriver` | Persists across tests unless reset | +| `Application.Keyboard` | Delegates to `ApplicationImpl.Instance.Keyboard` | +| `Application.Navigation` | Delegates to `ApplicationImpl.Instance.Navigation` | +| `Application.Popovers` | Delegates to `ApplicationImpl.Instance.Popovers` | +| `Application.Screen` | Delegates to `ApplicationImpl.Instance.Screen` | +| `Application.ResetState()` | Resets all static backing fields to defaults | +| `SynchronizationContext.Current` | Set by `Application.Init`; process-wide | + +### `ApplicationImpl` (singleton — `Terminal.Gui.App/ApplicationImpl.cs`) + +| Member | Notes | +|--------|-------| +| `ApplicationImpl.Instance` | Process-wide singleton; set by `SetInstance()` | +| `ApplicationImpl.SetInstance(impl)` | Replaces the singleton (used in tests) | +| `ApplicationImpl.ResetModelUsageTracking()` | Resets the static "which model was used" flag | + +### `ConfigurationManager` (singleton — `Terminal.Gui.Configuration/`) + +| Member | Notes | +|--------|-------| +| `CM.IsEnabled` | Global flag; affects all code reading configuration | +| `CM.Disable(reset)` | Disables and optionally resets to hard-coded defaults | + +### `View.Diagnostics` (static field — `Terminal.Gui.ViewBase/View.cs`) + +| Member | Notes | +|--------|-------| +| `View.Diagnostics` | `ViewDiagnosticFlags`; process-wide debug flags | + +--- + +## Which Project Does My Test Belong In? + +| Test characteristic | Project | +|---------------------|---------| +| No static state; no `Application.Init`/`Shutdown`; no `ConfigurationManager` mutations | `UnitTestsParallelizable` | +| Calls `Application.Init`/`Shutdown` directly, or mutates `Application.DefaultKeyBindings`, `Application.ForceDriver`, or `ApplicationImpl.ResetModelUsageTracking()` | `UnitTests.NonParallelizable` | +| Uses `[AutoInitShutdown]` or `[SetupFakeApplication]` (fake driver, does not call real `Application.Init`) | Currently in `UnitTests.Legacy`; migrate to `UnitTestsParallelizable` or `UnitTests.NonParallelizable` after review | +| Not yet reviewed / ported | `UnitTests.Legacy` | + +--- See the [Testing wiki](https://github.com/gui-cs/Terminal.Gui/wiki/Testing) for details on how to add more tests. diff --git a/Tests/UnitTests/AssemblyInfo.cs b/Tests/UnitTests.Legacy/AssemblyInfo.cs similarity index 100% rename from Tests/UnitTests/AssemblyInfo.cs rename to Tests/UnitTests.Legacy/AssemblyInfo.cs diff --git a/Tests/UnitTests/AutoInitShutdownAttribute.cs b/Tests/UnitTests.Legacy/AutoInitShutdownAttribute.cs similarity index 100% rename from Tests/UnitTests/AutoInitShutdownAttribute.cs rename to Tests/UnitTests.Legacy/AutoInitShutdownAttribute.cs diff --git a/Tests/UnitTests/DriverAssert.cs b/Tests/UnitTests.Legacy/DriverAssert.cs similarity index 100% rename from Tests/UnitTests/DriverAssert.cs rename to Tests/UnitTests.Legacy/DriverAssert.cs diff --git a/Tests/UnitTests/FakeDriver/FakeApplicationFactory.cs b/Tests/UnitTests.Legacy/FakeDriver/FakeApplicationFactory.cs similarity index 100% rename from Tests/UnitTests/FakeDriver/FakeApplicationFactory.cs rename to Tests/UnitTests.Legacy/FakeDriver/FakeApplicationFactory.cs diff --git a/Tests/UnitTests/FakeDriver/FakeApplicationLifecycle.cs b/Tests/UnitTests.Legacy/FakeDriver/FakeApplicationLifecycle.cs similarity index 100% rename from Tests/UnitTests/FakeDriver/FakeApplicationLifecycle.cs rename to Tests/UnitTests.Legacy/FakeDriver/FakeApplicationLifecycle.cs diff --git a/Tests/UnitTests/OutputAssert.cs b/Tests/UnitTests.Legacy/OutputAssert.cs similarity index 100% rename from Tests/UnitTests/OutputAssert.cs rename to Tests/UnitTests.Legacy/OutputAssert.cs diff --git a/Tests/UnitTests.Legacy/README.md b/Tests/UnitTests.Legacy/README.md new file mode 100644 index 0000000000..ad8be9df31 --- /dev/null +++ b/Tests/UnitTests.Legacy/README.md @@ -0,0 +1,13 @@ +# UnitTests.Legacy + +This project contains tests that have not yet been ported to `UnitTestsParallelizable` or `UnitTests.NonParallelizable`. + +**Do not add new tests here.** New tests should go in: +- [`../UnitTestsParallelizable`](../UnitTestsParallelizable/README.md) for tests with no static state dependency. +- [`../UnitTests.NonParallelizable`](../UnitTests.NonParallelizable/README.md) for tests that explicitly depend on process-wide static state. + +Each test class in this project is a candidate for one of: +1. **Rewrite** in the appropriate project if the case is not already covered. +2. **Deletion** if the case is already covered in `UnitTestsParallelizable`. + +See the [Testing wiki](https://github.com/gui-cs/Terminal.Gui/wiki/Testing) for details. diff --git a/Tests/UnitTests/SetupFakeApplicationAttribute.cs b/Tests/UnitTests.Legacy/SetupFakeApplicationAttribute.cs similarity index 100% rename from Tests/UnitTests/SetupFakeApplicationAttribute.cs rename to Tests/UnitTests.Legacy/SetupFakeApplicationAttribute.cs diff --git a/Tests/UnitTests/TestDriverBase.cs b/Tests/UnitTests.Legacy/TestDriverBase.cs similarity index 100% rename from Tests/UnitTests/TestDriverBase.cs rename to Tests/UnitTests.Legacy/TestDriverBase.cs diff --git a/Tests/UnitTests/TestLogging.cs b/Tests/UnitTests.Legacy/TestLogging.cs similarity index 100% rename from Tests/UnitTests/TestLogging.cs rename to Tests/UnitTests.Legacy/TestLogging.cs diff --git a/Tests/UnitTests/TestRespondersDisposedAttribute.cs b/Tests/UnitTests.Legacy/TestRespondersDisposedAttribute.cs similarity index 100% rename from Tests/UnitTests/TestRespondersDisposedAttribute.cs rename to Tests/UnitTests.Legacy/TestRespondersDisposedAttribute.cs diff --git a/Tests/UnitTests/TestsAllViews.cs b/Tests/UnitTests.Legacy/TestsAllViews.cs similarity index 100% rename from Tests/UnitTests/TestsAllViews.cs rename to Tests/UnitTests.Legacy/TestsAllViews.cs diff --git a/Tests/UnitTests/UnitTests.csproj b/Tests/UnitTests.Legacy/UnitTests.Legacy.csproj similarity index 98% rename from Tests/UnitTests/UnitTests.csproj rename to Tests/UnitTests.Legacy/UnitTests.Legacy.csproj index 1771097314..98cce1be6b 100644 --- a/Tests/UnitTests/UnitTests.csproj +++ b/Tests/UnitTests.Legacy/UnitTests.Legacy.csproj @@ -9,6 +9,7 @@ 2.0 + UnitTests.Legacy Exe true true diff --git a/Tests/UnitTests/UnitTests.csproj.DotSettings b/Tests/UnitTests.Legacy/UnitTests.Legacy.csproj.DotSettings similarity index 100% rename from Tests/UnitTests/UnitTests.csproj.DotSettings rename to Tests/UnitTests.Legacy/UnitTests.Legacy.csproj.DotSettings diff --git a/Tests/UnitTests/UnitTests.sln b/Tests/UnitTests.Legacy/UnitTests.Legacy.sln similarity index 87% rename from Tests/UnitTests/UnitTests.sln rename to Tests/UnitTests.Legacy/UnitTests.Legacy.sln index 0d950924ca..b7af8b233c 100644 --- a/Tests/UnitTests/UnitTests.sln +++ b/Tests/UnitTests.Legacy/UnitTests.Legacy.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests.csproj", "{A29633F2-B26E-48B2-997A-1733286E3C13}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests.Legacy", "UnitTests.Legacy.csproj", "{A29633F2-B26E-48B2-997A-1733286E3C13}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Tests/UnitTests/runsettings.coverage.xml b/Tests/UnitTests.Legacy/runsettings.coverage.xml similarity index 100% rename from Tests/UnitTests/runsettings.coverage.xml rename to Tests/UnitTests.Legacy/runsettings.coverage.xml diff --git a/Tests/UnitTests/runsettings.xml b/Tests/UnitTests.Legacy/runsettings.xml similarity index 100% rename from Tests/UnitTests/runsettings.xml rename to Tests/UnitTests.Legacy/runsettings.xml diff --git a/Tests/UnitTests/xunit.runner.json b/Tests/UnitTests.Legacy/xunit.runner.json similarity index 100% rename from Tests/UnitTests/xunit.runner.json rename to Tests/UnitTests.Legacy/xunit.runner.json diff --git a/Tests/UnitTests/Application/ApplicationModelFencingTests.cs b/Tests/UnitTests.NonParallelizable/Application/ApplicationModelFencingTests.cs similarity index 99% rename from Tests/UnitTests/Application/ApplicationModelFencingTests.cs rename to Tests/UnitTests.NonParallelizable/Application/ApplicationModelFencingTests.cs index eaaf6d3728..a0b6cb1a9f 100644 --- a/Tests/UnitTests/Application/ApplicationModelFencingTests.cs +++ b/Tests/UnitTests.NonParallelizable/Application/ApplicationModelFencingTests.cs @@ -1,4 +1,4 @@ -namespace UnitTests.ApplicationTests; +namespace UnitTests.NonParallelizable.ApplicationTests; /// /// Tests to ensure that mixing legacy static Application and modern instance-based models diff --git a/Tests/UnitTests/Application/Keyboard/ApplicationKeyboardThreadSafetyTests.cs b/Tests/UnitTests.NonParallelizable/Application/Keyboard/ApplicationKeyboardThreadSafetyTests.cs similarity index 99% rename from Tests/UnitTests/Application/Keyboard/ApplicationKeyboardThreadSafetyTests.cs rename to Tests/UnitTests.NonParallelizable/Application/Keyboard/ApplicationKeyboardThreadSafetyTests.cs index 8e170bc6fd..d29437dd76 100644 --- a/Tests/UnitTests/Application/Keyboard/ApplicationKeyboardThreadSafetyTests.cs +++ b/Tests/UnitTests.NonParallelizable/Application/Keyboard/ApplicationKeyboardThreadSafetyTests.cs @@ -1,7 +1,7 @@ #nullable enable // ReSharper disable AccessToDisposedClosure -namespace UnitTests.ApplicationTests.Keyboard; +namespace UnitTests.NonParallelizable.ApplicationTests.Keyboard; /// /// Tests to verify that ApplicationKeyboard is thread-safe for concurrent access scenarios. diff --git a/Tests/UnitTests/Application/Keyboard/KeyboardSetterTests.cs b/Tests/UnitTests.NonParallelizable/Application/Keyboard/KeyboardSetterTests.cs similarity index 98% rename from Tests/UnitTests/Application/Keyboard/KeyboardSetterTests.cs rename to Tests/UnitTests.NonParallelizable/Application/Keyboard/KeyboardSetterTests.cs index 6388369021..ca3f4fa1d7 100644 --- a/Tests/UnitTests/Application/Keyboard/KeyboardSetterTests.cs +++ b/Tests/UnitTests.NonParallelizable/Application/Keyboard/KeyboardSetterTests.cs @@ -1,7 +1,7 @@ // Tests that mutate Application.DefaultKeyBindings (static state). // These MUST NOT be in UnitTestsParallelizable. -namespace UnitTests.ApplicationTests.Keyboard; +namespace UnitTests.NonParallelizable.ApplicationTests.Keyboard; public class KeyboardSetterTests { diff --git a/Tests/UnitTests.NonParallelizable/Application/SynchronizationContextTests.cs b/Tests/UnitTests.NonParallelizable/Application/SynchronizationContextTests.cs new file mode 100644 index 0000000000..ab3641dee8 --- /dev/null +++ b/Tests/UnitTests.NonParallelizable/Application/SynchronizationContextTests.cs @@ -0,0 +1,52 @@ +// Copilot +#nullable enable + +namespace UnitTests.NonParallelizable.ApplicationTests; + +/// +/// Tests for the that is set during the Terminal.Gui application lifecycle. +/// Must run non-concurrently because and mutate +/// the process-wide . +/// +public class SynchronizationContextTests +{ + [Fact] + public void Init_SetsSynchronizationContext_Dispose_ClearsIt () + { + IApplication app = Application.Create (); + + try + { + app.Init (DriverRegistry.Names.ANSI); + + Assert.NotNull (SynchronizationContext.Current); + } + finally + { + app.Dispose (); + } + + Assert.Null (SynchronizationContext.Current); + } + + [Fact] + public void Init_SynchronizationContext_CreateCopy_ReturnsDifferentInstance () + { + IApplication app = Application.Create (); + + try + { + app.Init (DriverRegistry.Names.ANSI); + + SynchronizationContext context = SynchronizationContext.Current!; + SynchronizationContext copy = context.CreateCopy (); + + Assert.NotNull (copy); + Assert.NotSame (context, copy); + } + finally + { + app.Dispose (); + } + } +} diff --git a/Tests/UnitTests.NonParallelizable/AssemblyInfo.cs b/Tests/UnitTests.NonParallelizable/AssemblyInfo.cs new file mode 100644 index 0000000000..e6cc8f6e5c --- /dev/null +++ b/Tests/UnitTests.NonParallelizable/AssemblyInfo.cs @@ -0,0 +1,15 @@ +global using Attribute = Terminal.Gui.Drawing.Attribute; +global using Color = Terminal.Gui.Drawing.Color; +global using CM = Terminal.Gui.Configuration.ConfigurationManager; +global using Terminal.Gui.App; +global using Terminal.Gui.Time; +global using Terminal.Gui.Testing; +global using Terminal.Gui.Drivers; +global using Terminal.Gui.Input; +global using Terminal.Gui.Configuration; +global using Terminal.Gui.ViewBase; +global using Terminal.Gui.Views; +global using Terminal.Gui.Drawing; +global using Terminal.Gui.Text; +global using Terminal.Gui.Resources; +global using Terminal.Gui.FileServices; diff --git a/Tests/UnitTests/Configuration/ConfigurationMangerTests.cs b/Tests/UnitTests.NonParallelizable/Configuration/ConfigurationMangerTests.cs similarity index 99% rename from Tests/UnitTests/Configuration/ConfigurationMangerTests.cs rename to Tests/UnitTests.NonParallelizable/Configuration/ConfigurationMangerTests.cs index 7bfeaf2f84..81b77f8f80 100644 --- a/Tests/UnitTests/Configuration/ConfigurationMangerTests.cs +++ b/Tests/UnitTests.NonParallelizable/Configuration/ConfigurationMangerTests.cs @@ -10,9 +10,9 @@ #pragma warning disable IDE1006 -namespace UnitTests.ConfigurationTests; +namespace UnitTests.NonParallelizable.ConfigurationTests; -public class ConfigurationManagerTests (ITestOutputHelper output) +public class ConfigurationMangerTests (ITestOutputHelper output) { [Fact] public void ModuleInitializer_Was_Called () @@ -684,7 +684,7 @@ public void ResetToHardCodedDefaults_Resets () } } ] -} +} "; // ResetToCurrentValues (); @@ -1073,7 +1073,7 @@ public void InvalidJsonLogs () // "brown" is not a color var json = @" { - ""Themes"" : [ + ""Themes"" : [ { ""Default"" : { ""Schemes"": [ @@ -1096,7 +1096,7 @@ public void InvalidJsonLogs () // AbNormal is not a Scheme attribute json = @" { - ""Themes"" : [ + ""Themes"" : [ { ""Default"" : { ""Schemes"": [ @@ -1119,7 +1119,7 @@ public void InvalidJsonLogs () // Modify hotNormal background only json = @" { - ""Themes"" : [ + ""Themes"" : [ { ""Default"" : { ""Schemes"": [ @@ -1182,7 +1182,7 @@ public void InvalidJsonThrows () // AbNormal is not a Scheme attribute json = @" { - ""Themes"" : [ + ""Themes"" : [ { ""Default"" : { ""Schemes"": [ @@ -1206,7 +1206,7 @@ public void InvalidJsonThrows () // Modify hotNormal background only json = @" { - ""Themes"" : [ + ""Themes"" : [ { ""Default"" : { ""Schemes"": [ @@ -1386,7 +1386,7 @@ public void SourcesManager_Load_FromJson_Loads () } } ] -} +} "; //ResetToCurrentValues (); diff --git a/Tests/UnitTests/Configuration/GlyphTests.cs b/Tests/UnitTests.NonParallelizable/Configuration/GlyphTests.cs similarity index 96% rename from Tests/UnitTests/Configuration/GlyphTests.cs rename to Tests/UnitTests.NonParallelizable/Configuration/GlyphTests.cs index 6112093796..2cc49b3f5d 100644 --- a/Tests/UnitTests/Configuration/GlyphTests.cs +++ b/Tests/UnitTests.NonParallelizable/Configuration/GlyphTests.cs @@ -3,7 +3,7 @@ using System.Text.Json; using static Terminal.Gui.Configuration.ConfigurationManager; -namespace UnitTests.ConfigurationTests; +namespace UnitTests.NonParallelizable.ConfigurationTests; public class GlyphTests { diff --git a/Tests/UnitTests.NonParallelizable/README.md b/Tests/UnitTests.NonParallelizable/README.md new file mode 100644 index 0000000000..034e109360 --- /dev/null +++ b/Tests/UnitTests.NonParallelizable/README.md @@ -0,0 +1,24 @@ +# UnitTests.NonParallelizable + +This project contains tests that must not run concurrently with other tests because they read or write +process-wide static state. + +## When to add tests here + +Add a test here if it does **any** of the following: + +- Calls `Application.Init()` or `Application.Shutdown()` directly (not through `SetupFakeApplication`). +- Mutates `Application.DefaultKeyBindings` (a process-wide static dictionary). +- Calls `ApplicationImpl.ResetModelUsageTracking()`. +- Calls `Application.Create()` and tests the static-vs-instance model fencing. +- Sets `SynchronizationContext.Current` or depends on it being in a particular state. +- Otherwise cannot be run concurrently with other tests due to shared mutable static state. + +## What does NOT belong here + +- Tests that use `[SetupFakeApplication]` or `ApplicationImpl.SetInstance()` — those use a fake driver + instance and do not touch global static state in a way that prevents parallelism. They may belong in + `UnitTestsParallelizable` after review. +- New feature tests — those belong in `UnitTestsParallelizable`. + +See the [Testing wiki](https://github.com/gui-cs/Terminal.Gui/wiki/Testing) for details. diff --git a/Tests/UnitTests.NonParallelizable/UnitTests.NonParallelizable.csproj b/Tests/UnitTests.NonParallelizable/UnitTests.NonParallelizable.csproj new file mode 100644 index 0000000000..76f1c2b6bf --- /dev/null +++ b/Tests/UnitTests.NonParallelizable/UnitTests.NonParallelizable.csproj @@ -0,0 +1,61 @@ + + + + + + 2.0 + 2.0 + 2.0 + 2.0 + + + UnitTests.NonParallelizable + Exe + true + true + false + true + true + true + portable + $(DefineConstants);JETBRAINS_ANNOTATIONS;CONTRACTS_FULL + enable + true + $(NoWarn);AD0001 + true + + + true + $(DefineConstants);DEBUG_IDISPOSABLE + + + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + PreserveNewest + + + + + + + + diff --git a/Tests/UnitTests.NonParallelizable/xunit.runner.json b/Tests/UnitTests.NonParallelizable/xunit.runner.json new file mode 100644 index 0000000000..d47a70ea08 --- /dev/null +++ b/Tests/UnitTests.NonParallelizable/xunit.runner.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false, + "parallelizeAssembly": false, + "stopOnFail": false +} diff --git a/Tests/UnitTests/Application/ApplicationForceDriverTests.cs b/Tests/UnitTests/Application/ApplicationForceDriverTests.cs deleted file mode 100644 index 7d3ab3e118..0000000000 --- a/Tests/UnitTests/Application/ApplicationForceDriverTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using UnitTests; - -namespace UnitTests.ApplicationTests; - -public class ApplicationForceDriverTests : TestDriverBase -{ - [Fact (Skip = "Bogus test now that config properties are handled correctly")] - public void ForceDriver_Does_Not_Changes_If_It_Has_Valid_Value () - { - Assert.False (Application.Initialized); - Assert.Null (Application.Driver); - Assert.Equal (string.Empty, Application.ForceDriver); - - Application.ForceDriver = DriverRegistry.Names.ANSI; - Assert.Equal (DriverRegistry.Names.ANSI, Application.ForceDriver); - - Application.ForceDriver = DriverRegistry.Names.DOTNET; - Assert.Equal (DriverRegistry.Names.ANSI, Application.ForceDriver); - } - - [Fact (Skip = "Bogus test now that config properties are handled correctly")] - public void ForceDriver_Throws_If_Initialized_Changed_To_Another_Value () - { - IDriver driver = CreateTestDriver (); - - Assert.False (Application.Initialized); - Assert.Null (Application.Driver); - Assert.Equal (string.Empty, Application.ForceDriver); - - Application.Init (driverName: DriverRegistry.Names.ANSI); - Assert.True (Application.Initialized); - Assert.NotNull (Application.Driver); - Assert.Equal (DriverRegistry.Names.ANSI, Application.Driver.GetName ()); - Assert.Equal (string.Empty, Application.ForceDriver); - - Assert.Throws (() => Application.ForceDriver = "dotnet"); - - Application.ForceDriver = DriverRegistry.Names.ANSI; - Assert.Equal (DriverRegistry.Names.ANSI, Application.ForceDriver); - } -} diff --git a/Tests/UnitTests/Application/ApplicationPopoverTests.cs b/Tests/UnitTests/Application/ApplicationPopoverTests.cs deleted file mode 100644 index 6d6ba01877..0000000000 --- a/Tests/UnitTests/Application/ApplicationPopoverTests.cs +++ /dev/null @@ -1,370 +0,0 @@ -#nullable enable -namespace UnitTests.ApplicationTests; - -public class ApplicationPopoverTests -{ - [Fact] - public void Application_Init_Initializes_PopoverManager () - { - try - { - // Arrange - Application.Init (DriverRegistry.Names.ANSI); - - // Act - Assert.NotNull (Application.Popovers); - } - finally - { - Application.Shutdown (); - } - } - - [Fact] - public void Application_Shutdown_Resets_PopoverManager () - { - try - { - // Arrange - - Application.Init (DriverRegistry.Names.ANSI); - - // Act - Assert.NotNull (Application.Popovers); - - Application.Shutdown (); - - // Test - } - finally - { - Application.Shutdown (); - } - } - - [Fact] - public void Application_End_Does_Not_Reset_PopoverManager () - { - Runnable? top = null; - - try - { - // Arrange - Application.Init (DriverRegistry.Names.ANSI); - Assert.NotNull (Application.Popovers); - Application.StopAfterFirstIteration = true; - - top = new (); - SessionToken rs = Application.Begin (top); - - // Act - Application.End (rs); - - // Test - Assert.NotNull (Application.Popovers); - } - finally - { - top?.Dispose (); - Application.Shutdown (); - } - } - - [Fact] - public void Application_End_Hides_Active () - { - Runnable? top = null; - - try - { - // Arrange - Application.Init (DriverRegistry.Names.ANSI); - Application.StopAfterFirstIteration = true; - - top = new (); - SessionToken rs = Application.Begin (top); - - PopoverTestClass? popover = new (); - - Application.Popovers?.Register (popover); - Application.Popovers?.Show (popover); - Assert.True (popover.Visible); - - // Act - Application.End (rs); - - // Test - Assert.False (popover.Visible); - Assert.NotNull (Application.Popovers); - - popover.Dispose (); - Assert.Equal (1, popover.DisposedCount); - } - finally - { - top?.Dispose (); - Application.Shutdown (); - } - } - - [Fact] - public void Application_Shutdown_Disposes_Registered_Popovers () - { - try - { - // Arrange - - Application.Init (DriverRegistry.Names.ANSI); - - PopoverTestClass? popover = new (); - - // Act - Application.Popovers?.Register (popover); - Application.Shutdown (); - - // Test - Assert.Equal (1, popover.DisposedCount); - } - finally - { - Application.Shutdown (); - } - } - - [Fact] - public void Application_Shutdown_Does_Not_Dispose_DeRegistered_Popovers () - { - try - { - // Arrange - - Application.Init (DriverRegistry.Names.ANSI); - - PopoverTestClass? popover = new (); - - Application.Popovers?.Register (popover); - - // Act - Application.Popovers?.DeRegister (popover); - Application.Shutdown (); - - // Test - Assert.Equal (0, popover.DisposedCount); - - popover.Dispose (); - Assert.Equal (1, popover.DisposedCount); - } - finally - { - Application.Shutdown (); - } - } - - [Fact] - public void Application_Shutdown_Does_Not_Dispose_ActiveNotRegistered_Popover () - { - try - { - // Arrange - - Application.Init (DriverRegistry.Names.ANSI); - - PopoverTestClass? popover = new (); - Application.Popovers?.Register (popover); - Application.Popovers?.Show (popover); - Application.Popovers?.DeRegister (popover); - - // Act - Application.Shutdown (); - - // Test - Assert.Equal (0, popover.DisposedCount); - - popover.Dispose (); - Assert.Equal (1, popover.DisposedCount); - } - finally - { - Application.Shutdown (); - } - } - - [Fact] - public void Register_SetsRunnable () - { - try - { - // Arrange - - Application.Init (DriverRegistry.Names.ANSI); - Application.Begin (new Runnable ()); - PopoverTestClass? popover = new (); - - // Act - Application.Popovers?.Register (popover); - - // Assert - Assert.Equal (Application.TopRunnableView as IRunnable, popover.Owner); - } - finally - { - Application.TopRunnableView?.Dispose (); - Application.Shutdown (); - } - } - - [Fact] - public void Keyboard_Events_Go_Only_To_Popover_Associated_With_Runnable () - { - try - { - // Arrange - Application.Init (DriverRegistry.Names.ANSI); - - Runnable? initialRunnable = new () { Id = "initialRunnable" }; - Application.Begin (initialRunnable); - PopoverTestClass? popover = new (); - var keyDownEvents = 0; - - popover.KeyDown += (s, e) => - { - keyDownEvents++; - e.Handled = true; - }; // Ensure it handles the key - - Application.Popovers?.Register (popover); - - // Act - Application.RaiseKeyDownEvent (Key.A); // Goes to initialRunnable - - Runnable? secondaryRunnable = new () { Id = "secondaryRunnable" }; - Application.Begin (secondaryRunnable); - - Application.RaiseKeyDownEvent (Key.A); // Goes to secondaryRunnable - - // Test - Assert.Equal (1, keyDownEvents); - - popover.Dispose (); - Assert.Equal (1, popover.DisposedCount); - } - finally - { - Application.Shutdown (); - } - } - - // See: https://github.com/gui-cs/Terminal.Gui/issues/4122 - [Theory] - [InlineData (0, 0, new [] { "runnable" })] - [InlineData (10, 10, new string [] { })] - [InlineData (1, 1, new [] { "runnable", "view" })] - [InlineData (5, 5, new [] { "runnable" })] - [InlineData (6, 6, new [] { "popoverSubView" })] - [InlineData (7, 7, new [] { "runnable" })] - [InlineData (3, 3, new [] { "runnable" })] - public void GetViewsUnderMouse_Supports_ActivePopover (int mouseX, int mouseY, string [] viewIdStrings) - { - PopoverTestClass? popover = null; - - try - { - // Arrange - Application.Init (DriverRegistry.Names.ANSI); - - Runnable? runnable = new () - { - Frame = new (0, 0, 10, 10), - Id = "runnable" - }; - Application.Begin (runnable); - - View? view = new () - { - Id = "view", - X = 1, - Y = 1, - Width = 2, - Height = 2 - }; - - runnable.Add (view); - - popover = new () - { - Id = "popover", - X = 5, - Y = 5, - Width = 3, - Height = 3 - }; // at 5,5 to 8,8 (screen) - - View? popoverSubView = new () - { - Id = "popoverSubView", - X = 1, - Y = 1, - Width = 1, - Height = 1 - }; - - popover.Add (popoverSubView); - Application.Popovers?.Register (popover); - - Application.Popovers?.Show (popover); - - List found = view.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); - - string [] foundIds = found.Select (v => v!.Id).ToArray (); - - Assert.Equal (viewIdStrings, foundIds); - } - finally - { - popover?.Dispose (); - Application.Shutdown (); - } - } - - public class PopoverTestClass : PopoverImpl - { - public List HandledKeys { get; } = []; - public int NewCommandInvokeCount { get; private set; } - -#if DEBUG_IDISPOSABLE - // NOTE: Hides the base DisposedCount property - public new int DisposedCount { get; private set; } -#else - public int DisposedCount { get; private set; } -#endif - public PopoverTestClass () - { - CanFocus = true; - AddCommand (Command.New, NewCommandHandler!); - HotKeyBindings.Add (Key.N.WithCtrl, Command.New); - - return; - - bool? NewCommandHandler (ICommandContext ctx) - { - NewCommandInvokeCount++; - - return false; - } - } - - protected override bool OnKeyDown (Key key) - { - HandledKeys.Add (key); - - return false; - } - - /// - protected override void Dispose (bool disposing) - { - base.Dispose (disposing); - DisposedCount++; - } - } -} diff --git a/Tests/UnitTests/Application/ApplicationScreenTests.cs b/Tests/UnitTests/Application/ApplicationScreenTests.cs deleted file mode 100644 index 0bc65ea801..0000000000 --- a/Tests/UnitTests/Application/ApplicationScreenTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace UnitTests.ApplicationTests; - -public class ApplicationScreenTests -{ - public ApplicationScreenTests (ITestOutputHelper output) { } - - [Fact] - [AutoInitShutdown] - public void ClearContents_Called_When_Top_Frame_Changes () - { - var top = new Runnable (); - SessionToken rs = Application.Begin (top); - - // Arrange - var clearedContentsRaised = 0; - - Application.Driver!.ClearedContents += OnClearedContents; - - // Act - Application.LayoutAndDraw (); - - // Assert - Assert.Equal (0, clearedContentsRaised); - - // Act - Application.TopRunnableView!.SetNeedsLayout (); - Application.LayoutAndDraw (); - - // Assert - Assert.Equal (0, clearedContentsRaised); - - // Act - Application.TopRunnableView.X = 1; - Application.LayoutAndDraw (); - - // Assert - Assert.Equal (1, clearedContentsRaised); - - // Act - Application.TopRunnableView.Width = 10; - Application.LayoutAndDraw (); - - // Assert - Assert.Equal (2, clearedContentsRaised); - - // Act - Application.TopRunnableView.Y = 1; - Application.LayoutAndDraw (); - - // Assert - Assert.Equal (3, clearedContentsRaised); - - // Act - Application.TopRunnableView.Height = 10; - Application.LayoutAndDraw (); - - // Assert - Assert.Equal (4, clearedContentsRaised); - - Application.Driver!.ClearedContents -= OnClearedContents; - - Application.End (rs); - - return; - - void OnClearedContents (object e, EventArgs a) { clearedContentsRaised++; } - } - - [Fact] - public void ClearScreenNextIteration_Resets_To_False_After_LayoutAndDraw () - { - // Arrange - Application.ResetState (true); - Application.Init (DriverRegistry.Names.ANSI); - - // Act - Application.ClearScreenNextIteration = true; - Application.LayoutAndDraw (); - - // Assert - Assert.False (Application.ClearScreenNextIteration); - - // Cleanup - Application.ResetState (true); - } - - [Fact] - [SetupFakeApplication] - public void Screen_Changes_OnScreenChanged_Without_Call_Application_Init () - { - Assert.Equal (new (0, 0, 80, 25), Application.Screen); - - // Act - Application.Driver!.SetScreenSize (120, 30); - - // Assert - Assert.Equal (new (0, 0, 120, 30), Application.Screen); - } -} diff --git a/Tests/UnitTests/Application/MainLoopTTests.cs b/Tests/UnitTests/Application/MainLoopTTests.cs deleted file mode 100644 index 5829179c66..0000000000 --- a/Tests/UnitTests/Application/MainLoopTTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Moq; - -namespace UnitTests.ApplicationTests; -public class MainLoopTTests -{ - //[Fact] - //public void MainLoopT_NotInitialized_Throws() - //{ - // var m = new MainLoop (); - - // Assert.Throws (() => m.TimedEvents); - // Assert.Throws (() => m.InputBuffer); - // Assert.Throws (() => m.InputProcessor); - // Assert.Throws (() => m.Out); - // Assert.Throws (() => m.AnsiRequestScheduler); - // Assert.Throws (() => m.WindowSizeMonitor); - - // var componentFactory = new Mock> (); - - // componentFactory.Setup ( - // c => c.CreateWindowSizeMonitor ( - // It.IsAny (), - // It.IsAny ())) - // .Returns (Mock.Of ()); - - // m.Initialize (new TimedEvents (), - // new ConcurrentQueue (), - // Mock.Of (), - // Mock.Of(), - // componentFactory.Object - // ); - - // Assert.NotNull (m.TimedEvents); - // Assert.NotNull (m.InputBuffer); - // Assert.NotNull (m.InputProcessor); - // Assert.NotNull (m.Out); - // Assert.NotNull (m.AnsiRequestScheduler); - // Assert.NotNull (m.WindowSizeMonitor); - //} -} diff --git a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs b/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs deleted file mode 100644 index 6f1c7855aa..0000000000 --- a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs +++ /dev/null @@ -1,397 +0,0 @@ -#nullable enable - -// Alias Console to MockConsole so we don't accidentally use Console - -namespace UnitTests.ApplicationTests; - -[Trait ("Category", "Input")] -public class ApplicationMouseTests -{ - private readonly ITestOutputHelper _output; - - public ApplicationMouseTests (ITestOutputHelper output) - { - _output = output; -#if DEBUG_IDISPOSABLE - View.Instances.Clear (); -#endif - } - - #region mouse coordinate tests - - // test Application.MouseEvent - ensure coordinates are screen relative - [Theory] - - // inside tests - [InlineData (0, 0, 0, 0, true)] - [InlineData (1, 0, 1, 0, true)] - [InlineData (0, 1, 0, 1, true)] - [InlineData (9, 0, 9, 0, true)] - [InlineData (0, 9, 0, 9, true)] - - // outside tests - [InlineData (-1, -1, -1, -1, true)] - [InlineData (0, -1, 0, -1, true)] - [InlineData (-1, 0, -1, 0, true)] - public void MouseEventCoordinatesAreScreenRelative (int clickX, int clickY, int expectedX, int expectedY, bool expectedClicked) - { - var mouse = new Mouse { ScreenPosition = new Point (clickX, clickY), Flags = MouseFlags.LeftButtonPressed }; - var clicked = false; - - void OnApplicationOnMouseEvent (object? s, Mouse e) - { - Assert.Equal (expectedX, e.ScreenPosition.X); - Assert.Equal (expectedY, e.ScreenPosition.Y); - clicked = true; - } - - Application.MouseEvent += OnApplicationOnMouseEvent; - - Application.RaiseMouseEvent (mouse); - Assert.Equal (expectedClicked, clicked); - Application.MouseEvent -= OnApplicationOnMouseEvent; - } - - /// - /// Tests that the mouse coordinates passed to the focused view are correct when the mouse is clicked. No adornments; - /// Frame == Viewport - /// - [Theory] - [AutoInitShutdown] - - // click inside view tests - [InlineData (0, 0, 0, 0, 0, true)] - [InlineData (0, 1, 0, 1, 0, true)] - [InlineData (0, 0, 1, 0, 1, true)] - [InlineData (0, 9, 0, 9, 0, true)] - [InlineData (0, 0, 9, 0, 9, true)] - - // view is offset from origin ; click is inside view - [InlineData (1, 1, 1, 0, 0, true)] - [InlineData (1, 2, 1, 1, 0, true)] - [InlineData (1, 1, 2, 0, 1, true)] - [InlineData (1, 9, 1, 8, 0, true)] - [InlineData (1, 1, 9, 0, 8, true)] - - // click outside view tests - [InlineData (0, -1, -1, 0, 0, false)] - [InlineData (0, 0, -1, 0, 0, false)] - [InlineData (0, -1, 0, 0, 0, false)] - [InlineData (0, 0, 10, 0, 0, false)] - [InlineData (0, 10, 0, 0, 0, false)] - [InlineData (0, 10, 10, 0, 0, false)] - - // view is offset from origin ; click is outside view - [InlineData (1, 0, 0, 0, 0, false)] - [InlineData (1, 1, 0, 0, 0, false)] - [InlineData (1, 0, 1, 0, 0, false)] - [InlineData (1, 9, 0, 0, 0, false)] - [InlineData (1, 0, 9, 0, 0, false)] - public void MouseCoordinatesTest_NoAdornments (int offset, int clickX, int clickY, int expectedX, int expectedY, bool expectedClicked) - { - Size size = new (10, 10); - Point pos = new (offset, offset); - - var clicked = false; - - var view = new View { X = pos.X, Y = pos.Y, Width = size.Width, Height = size.Height }; - - var mouseEventToRaise = new Mouse { ScreenPosition = new Point (clickX, clickY), Flags = MouseFlags.LeftButtonClicked }; - - view.MouseEvent += (s, mouse) => - { - Assert.Equal (expectedX, mouse.Position!.Value.X); - Assert.Equal (expectedY, mouse.Position!.Value.Y); - clicked = true; - }; - - var top = new Runnable (); - top.Add (view); - Application.Begin (top); - - Application.RaiseMouseEvent (mouseEventToRaise); - Assert.Equal (expectedClicked, clicked); - top.Dispose (); - } - - #endregion mouse coordinate tests - - #region mouse grab tests - - [Fact (Skip = "Rebuild to use ScrollBar")] - [AutoInitShutdown] - public void IsGrabbed_WithNullMouseEventView () - { - //var tf = new TextField { Width = 10 }; - //var sv = new ScrollView { Width = Dim.Fill (), Height = Dim.Fill () }; - //sv.SetContentSize (new (100, 100)); - - //sv.Add (tf); - //var top = new Runnable (); - //top.Add (sv); - - //int iterations = -1; - - //ApplicationImpl.Instance.Iteration += (s, a) => - // { - // iterations++; - - // if (iterations == 0) - // { - // Assert.True (tf.HasFocus); - // Assert.False (Application.Mouse.IsGrabbed (anyView)); - - // Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.PositionReport }); - - // Assert.True (Application.Mouse.IsGrabbed (sv)); - - // MessageBox.Query (App, "Title", "Test", "Ok"); - - // Assert.False (Application.Mouse.IsGrabbed (anyView)); - // } - // else if (iterations == 1) - // { - // // Application.Mouse.IsGrabbed is false because - // // another runnable (Dialog) was opened - // Assert.False (Application.Mouse.IsGrabbed (anyView)); - - // Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.PositionReport }); - - // Assert.False (Application.Mouse.IsGrabbed (anyView)); - - // Application.RaiseMouseEvent (new () { ScreenPosition = new (40, 12), Flags = MouseFlags.PositionReport }); - - // Assert.False (Application.Mouse.IsGrabbed (anyView)); - - // Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.LeftButtonPressed }); - - // Assert.False (Application.Mouse.IsGrabbed (anyView)); - - // Application.RequestStop (); - // } - // else if (iterations == 2) - // { - // Assert.False (Application.Mouse.IsGrabbed (anyView)); - - // Application.RequestStop (); - // } - // }; - - //Application.Run (top); - //top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void IsGrabbed_GrabbedMouse_UnGrabbedMouse () - { - View? grabView = null; - var count = 0; - - var view1 = new View { Id = "view1" }; - var view2 = new View { Id = "view2" }; - var view3 = new View { Id = "view3" }; - - Application.Mouse.GrabbedMouse += Application_GrabbedMouse; - Application.Mouse.UnGrabbedMouse += Application_UnGrabbedMouse; - - Application.Mouse.GrabMouse (view1); - Assert.Equal (0, count); - Assert.Equal (grabView, view1); - Assert.True (Application.Mouse.IsGrabbed (view1)); - - Application.Mouse.UngrabMouse (); - Assert.Equal (1, count); - Assert.Equal (grabView, view1); - Assert.False (Application.Mouse.IsGrabbed (view1)); - - Application.Mouse.GrabbedMouse += Application_GrabbedMouse; - Application.Mouse.UnGrabbedMouse += Application_UnGrabbedMouse; - - Application.Mouse.GrabMouse (view2); - Assert.Equal (1, count); - Assert.Equal (grabView, view2); - Assert.True (Application.Mouse.IsGrabbed (view2)); - - Application.Mouse.UngrabMouse (); - Assert.Equal (2, count); - Assert.Equal (grabView, view2); - Assert.True (Application.Mouse.IsGrabbed (view3)); - Application.Mouse.UngrabMouse (); - Assert.False (Application.Mouse.IsGrabbed (view3)); - - void Application_GrabbedMouse (object? sender, ViewEventArgs e) - { - if (count == 0) - { - Assert.Equal (view1, e.View); - grabView = view1; - } - else - { - Assert.Equal (view2, e.View); - grabView = view2; - } - - Application.Mouse.GrabbedMouse -= Application_GrabbedMouse; - } - - void Application_UnGrabbedMouse (object? sender, ViewEventArgs e) - { - if (count == 0) - { - Assert.Equal (view1, e.View); - Assert.Equal (grabView, e.View); - } - else - { - Assert.Equal (view2, e.View); - Assert.Equal (grabView, e.View); - } - - count++; - - if (count > 1) - { - // It's possible to grab another view after the previous was ungrabbed - Application.Mouse.GrabMouse (view3); - } - - Application.Mouse.UnGrabbedMouse -= Application_UnGrabbedMouse; - } - } - - [Fact] - [AutoInitShutdown] - public void IsGrabbed_Parameterless_ReturnsTrueWhenAnyViewGrabbed () - { - var view1 = new View { Id = "view1" }; - var view2 = new View { Id = "view2" }; - - // Initially no view has the grab - Assert.False (Application.Mouse.IsGrabbed ()); - - // Grab view1 - Application.Mouse.GrabMouse (view1); - Assert.True (Application.Mouse.IsGrabbed ()); - Assert.True (Application.Mouse.IsGrabbed (view1)); - Assert.False (Application.Mouse.IsGrabbed (view2)); - - // Ungrab - Application.Mouse.UngrabMouse (); - Assert.False (Application.Mouse.IsGrabbed ()); - Assert.False (Application.Mouse.IsGrabbed (view1)); - - // Grab view2 - Application.Mouse.GrabMouse (view2); - Assert.True (Application.Mouse.IsGrabbed ()); - Assert.False (Application.Mouse.IsGrabbed (view1)); - Assert.True (Application.Mouse.IsGrabbed (view2)); - - // Ungrab - Application.Mouse.UngrabMouse (); - Assert.False (Application.Mouse.IsGrabbed ()); - - view1.Dispose (); - view2.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void IsGrabbed_Parameterless_ReturnsFalseAfterViewDisposed () - { - var view = new View { Id = "view" }; - - Application.Mouse.GrabMouse (view); - Assert.True (Application.Mouse.IsGrabbed ()); - Assert.True (Application.Mouse.IsGrabbed (view)); - - // Dispose without ungrabbing - WeakReference should handle this - view.Dispose (); - - // After disposal, the WeakReference may still hold the reference until GC - // But once GC runs, IsGrabbed() should return false - // For deterministic testing, we ungrab first - Application.Mouse.UngrabMouse (); - Assert.False (Application.Mouse.IsGrabbed ()); - } - - [Fact] - [AutoInitShutdown] - public void View_Is_Responsible_For_Calling_UnGrabMouse_Before_Being_Disposed () - { - var count = 0; - var view = new View { Width = 1, Height = 1 }; - view.MouseEvent += (s, e) => count++; - var top = new Runnable (); - top.Add (view); - Application.Begin (top); - - Assert.False (Application.Mouse.IsGrabbed (view)); - Application.Mouse.GrabMouse (view); - Assert.True (Application.Mouse.IsGrabbed (view)); - top.Remove (view); - Application.Mouse.UngrabMouse (); - view.Dispose (); -#if DEBUG_IDISPOSABLE - Assert.True (view.WasDisposed); -#endif - - Application.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (0, 0), Flags = MouseFlags.LeftButtonPressed }); - Assert.False (Application.Mouse.IsGrabbed (view)); - Assert.Equal (0, count); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void MouseGrab_EventSentToGrabView_HasCorrectView () - { - // BEFORE FIX: viewRelativeMouseEvent.View = deepestViewUnderMouse ?? IsGrabbed (potentially targetView). - // AFTER FIX: viewRelativeMouseEvent.View = IsGrabbed (always the grab view). - // Test fails before fix (receivedView == targetView), passes after fix (receivedView == grabView). - - var grabView = new View - { - Id = "grab", - X = 0, - Y = 0, - Width = 5, - Height = 5 - }; - - var targetView = new View - { - Id = "target", - X = 0, - Y = 0, - Width = 5, - Height = 5 - }; - - View? receivedView = null; - grabView.MouseEvent += (_, e) => receivedView = e.View; - - var top = new Runnable { Width = 20, Height = 10 }; - top.Add (grabView); - top.Add (targetView); // deepestViewUnderMouse = targetView - Application.Begin (top); - - Application.Mouse.GrabMouse (grabView); - Assert.True (Application.Mouse.IsGrabbed (grabView)); - - Application.RaiseMouseEvent (new Mouse - { - ScreenPosition = new Point (2, 2), // Inside both views - Flags = MouseFlags.LeftButtonClicked - }); - - // EXPECTED: Event sent to grab view has View == grabView. - Assert.Equal (grabView, receivedView); - - Application.Mouse.UngrabMouse (); - top.Dispose (); - } - - #endregion -} diff --git a/Tests/UnitTests/Application/SynchronizatonContextTests.cs b/Tests/UnitTests/Application/SynchronizatonContextTests.cs deleted file mode 100644 index e124b810bc..0000000000 --- a/Tests/UnitTests/Application/SynchronizatonContextTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Alias Console to MockConsole so we don't accidentally use Console - -using UnitTests; - -namespace UnitTests.ApplicationTests; - -public class SyncrhonizationContextTests -{ - [Fact] - public void SynchronizationContext_CreateCopy () - { - Application.Init (DriverRegistry.Names.ANSI); - SynchronizationContext context = SynchronizationContext.Current; - Assert.NotNull (context); - - SynchronizationContext contextCopy = context.CreateCopy (); - Assert.NotNull (contextCopy); - - Assert.NotEqual (context, contextCopy); - Application.Shutdown (); - } - - private readonly object _lockPost = new (); - - [Theory] - [InlineData (DriverRegistry.Names.ANSI)] - [InlineData (DriverRegistry.Names.WINDOWS)] - [InlineData (DriverRegistry.Names.DOTNET)] - public void SynchronizationContext_Post (string driverName = null) - { - lock (_lockPost) - { - Application.Init (driverName); - - SynchronizationContext context = SynchronizationContext.Current; - - var success = false; - - Task.Run (() => - { - while (Application.TopRunnable is { IsRunning: false }) - { - Thread.Sleep (500); - } - - // non blocking - context.Post (delegate - { - success = true; - - // then tell the application to quit - Application.Invoke (() => Application.RequestStop ()); - }, - null); - - if (Application.TopRunnable is { IsRunning: true }) - { - Assert.False (success); - } - }, - TestContext.Current.CancellationToken); - - // blocks here until the RequestStop is processed at the end of the test - Application.Run (); - Assert.True (success); - - Application.Shutdown (); - } - } - - [Fact] - [AutoInitShutdown] - public void SynchronizationContext_Send () - { - SynchronizationContext context = SynchronizationContext.Current; - - var success = false; - - Task.Run (() => - { - Thread.Sleep (500); - - // blocking - context.Send (delegate - { - success = true; - - // then tell the application to quit - Application.Invoke (() => Application.RequestStop ()); - }, - null); - Assert.True (success); - }, - TestContext.Current.CancellationToken); - - // blocks here until the RequestStop is processed at the end of the test - Application.Run (); - Assert.True (success); - Application.Shutdown (); - } -} diff --git a/Tests/UnitTests/Application/TimedEventsTests.cs b/Tests/UnitTests/Application/TimedEventsTests.cs deleted file mode 100644 index ffce360276..0000000000 --- a/Tests/UnitTests/Application/TimedEventsTests.cs +++ /dev/null @@ -1,10 +0,0 @@ -#nullable enable -namespace UnitTests.ApplicationTests; - -/// -/// Tests for TimedEvents class, focusing on high-resolution timing with Stopwatch. -/// -public class TimedEventsTests -{ - -} diff --git a/Tests/UnitTests/Clipboard/ClipboardTests.cs b/Tests/UnitTests/Clipboard/ClipboardTests.cs deleted file mode 100644 index 13b690ad10..0000000000 --- a/Tests/UnitTests/Clipboard/ClipboardTests.cs +++ /dev/null @@ -1,340 +0,0 @@ -namespace UnitTests.ClipboardTests; - -#if RUN_CLIPBOARD_UNIT_TESTS -public class ClipboardTests -{ - readonly ITestOutputHelper output; - public ClipboardTests (ITestOutputHelper output) { this.output = output; } - - [Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)] - public void IClipboard_GetClipBoardData_Throws_NotSupportedException () - { - var iclip = Application.Driver?.Clipboard; - Assert.Throws (() => iclip.GetClipboardData ()); - } - - [Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)] - public void IClipboard_SetClipBoardData_Throws_NotSupportedException () - { - var iclip = Application.Driver?.Clipboard; - Assert.Throws (() => iclip.SetClipboardData ("foo")); - } - - [Fact, AutoInitShutdown (useFakeClipboard: true)] - public void IApplication_Clipboard_Property_Works () - { - if (Application.Clipboard?.IsSupported != true) - { - output.WriteLine ($"The Clipboard not supported on this platform."); - return; - } - - string clipText = "The IApplication_Clipboard_Property_Works unit test pasted this to the OS clipboard."; - - // Use the new IApplication.Clipboard property - Application.Clipboard.SetClipboardData (clipText); - - ApplicationImpl.Instance.Iteration += (s, a) => ApplicationImpl.Instance.RequestStop (); - ApplicationImpl.Instance.Run>().Dispose(); - - Assert.True(Application.Clipboard.TryGetClipboardData(out string result)); - Assert.Equal (clipText, result); - } - - [Fact, AutoInitShutdown (useFakeClipboard: true)] - public void Contents_Fake_Gets_Sets () - { - if (!Clipboard.IsSupported) - { - output.WriteLine ($"The Clipboard not supported on this platform."); - - return; - } - - string clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; - Clipboard.Contents = clipText; - - ApplicationImpl.Instance.Iteration += (s, a) => Application.RequestStop (); - Application.Run (); - - Assert.Equal (clipText, Clipboard.Contents); - } - - [Fact, AutoInitShutdown (useFakeClipboard: false)] - public void Contents_Gets_Sets () - { - if (!Clipboard.IsSupported) - { - output.WriteLine ($"The Clipboard not supported on this platform."); - - return; - } - - string clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; - Clipboard.Contents = clipText; - - ApplicationImpl.Instance.Iteration += (s, a) => Application.RequestStop (); - Application.Run (); - - Assert.Equal (clipText, Clipboard.Contents); - } - - [Fact, AutoInitShutdown (useFakeClipboard: false)] - public void Contents_Gets_Sets_When_IsSupportedFalse () - { - if (!Clipboard.IsSupported) - { - output.WriteLine ($"The Clipboard not supported on this platform."); - - return; - } - - string clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; - Clipboard.Contents = clipText; - - ApplicationImpl.Instance.Iteration += (s, a) => Application.RequestStop (); - Application.Run (); - - Assert.Equal (clipText, Clipboard.Contents); - } - - [Fact, AutoInitShutdown (useFakeClipboard: true)] - public void Contents_Fake_Gets_Sets_When_IsSupportedFalse () - { - if (!Clipboard.IsSupported) - { - output.WriteLine ($"The Clipboard not supported on this platform."); - - return; - } - - string clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; - Clipboard.Contents = clipText; - - ApplicationImpl.Instance.Iteration += (s, a) => Application.RequestStop (); - Application.Run (); - - Assert.Equal (clipText, Clipboard.Contents); - } - - [Fact, AutoInitShutdown (useFakeClipboard: false)] - public void IsSupported_Get () - { - if (Clipboard.IsSupported) - { - Assert.True (Clipboard.IsSupported); - } - else - { - Assert.False (Clipboard.IsSupported); - } - } - - [Fact, AutoInitShutdown (useFakeClipboard: false)] - public void TryGetClipboardData_Gets_From_OS_Clipboard () - { - string clipText = "The TryGetClipboardData_Gets_From_OS_Clipboard unit test pasted this to the OS clipboard."; - Clipboard.Contents = clipText; - - ApplicationImpl.Instance.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (); - - if (Clipboard.IsSupported) - { - Assert.True (Clipboard.TryGetClipboardData (out string result)); - Assert.Equal (clipText, result); - } - else - { - Assert.False (Clipboard.TryGetClipboardData (out string result)); - Assert.NotEqual (clipText, result); - } - } - - [Fact, AutoInitShutdown (useFakeClipboard: false)] - public void TrySetClipboardData_Sets_The_OS_Clipboard () - { - string clipText = "The TrySetClipboardData_Sets_The_OS_Clipboard unit test pasted this to the OS clipboard."; - - if (Clipboard.IsSupported) - { - Assert.True (Clipboard.TrySetClipboardData (clipText)); - } - else - { - Assert.False (Clipboard.TrySetClipboardData (clipText)); - } - - ApplicationImpl.Instance.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (); - - if (Clipboard.IsSupported) - { - Assert.Equal (clipText, Clipboard.Contents); - } - else - { - Assert.NotEqual (clipText, Clipboard.Contents); - } - } - - // Disabling this test for now because it is not reliable -#if false - [Fact, AutoInitShutdown (useFakeClipboard: false)] - public void Contents_Copies_From_OS_Clipboard () - { - if (!Clipboard.IsSupported) { - output.WriteLine ($"The Clipboard not supported on this platform."); - return; - } - - var clipText = "The Contents_Copies_From_OS_Clipboard unit test pasted this to the OS clipboard."; - var failed = false; - var getClipText = ""; - - ApplicationImpl.Instance.Iteration += (s, a) => { - int exitCode = 0; - string result = ""; - output.WriteLine ($"Pasting to OS clipboard: {clipText}..."); - - if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { - (exitCode, result) = - ClipboardProcessRunner.Process ("powershell.exe", $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\""); - output.WriteLine ($" Windows: powershell.exe Set-Clipboard: exitCode = {exitCode}, result = {result}"); - getClipText = Clipboard.Contents; - - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { - (exitCode, result) = ClipboardProcessRunner.Process ("pbcopy", string.Empty, clipText); - output.WriteLine ($" OSX: pbcopy: exitCode = {exitCode}, result = {result}"); - getClipText = Clipboard.Contents; - - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) { - if (Is_WSL_Platform ()) { - try { - // This runs the WINDOWS version of powershell.exe via WSL. - (exitCode, result) = - ClipboardProcessRunner.Process ("powershell.exe", $"-noprofile -command \"Set-Clipboard -Value \\\"{clipText}\\\"\""); - output.WriteLine ($" WSL: powershell.exe Set-Clipboard: exitCode = {exitCode}, result = {result}"); - } catch { - failed = true; - } - - if (!failed) { - // If we set the OS clipboard via Powershell, then getting Contents should return the same text. - getClipText = Clipboard.Contents; - output.WriteLine ($" WSL: Clipboard.Contents: {getClipText}"); - } - Application.RequestStop (); - return; - } - - if (failed = xclipExists () == false) { - // if xclip doesn't exist then exit. - output.WriteLine ($" WSL: no xclip found."); - Application.RequestStop (); - return; - } - - // If we get here, powershell didn't work and xclip exists... - (exitCode, result) = - ClipboardProcessRunner.Process ("bash", $"-c \"xclip -sel clip -i\"", clipText); - output.WriteLine ($" Linux: bash xclip -sel clip -i: exitCode = {exitCode}, result = {result}"); - - if (!failed) { - getClipText = Clipboard.Contents; - output.WriteLine ($" Linux via xclip: Clipboard.Contents: {getClipText}"); - } - } - - Application.RequestStop (); - }; - - Application.Run (); - - if (!failed) Assert.Equal (clipText, getClipText); - } - - [Fact, AutoInitShutdown (useFakeClipboard: false)] - public void Contents_Pastes_To_OS_Clipboard () - { - if (!Clipboard.IsSupported) { - output.WriteLine ($"The Clipboard not supported on this platform."); - return; - } - - var clipText = "The Contents_Pastes_To_OS_Clipboard unit test pasted this via Clipboard.Contents."; - var clipReadText = ""; - var failed = false; - - ApplicationImpl.Instance.Iteration += (s, a) => { - Clipboard.Contents = clipText; - - int exitCode = 0; - output.WriteLine ($"Getting OS clipboard..."); - - if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { - (exitCode, clipReadText) = - ClipboardProcessRunner.Process ("powershell.exe", "-noprofile -command \"Get-Clipboard\""); - output.WriteLine ($" Windows: powershell.exe Get-Clipboard: exitCode = {exitCode}, result = {clipReadText}"); - - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { - (exitCode, clipReadText) = ClipboardProcessRunner.Process ("pbpaste", ""); - output.WriteLine ($" OSX: pbpaste: exitCode = {exitCode}, result = {clipReadText}"); - - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) { - if (Is_WSL_Platform ()) { - (exitCode, clipReadText) = - ClipboardProcessRunner.Process ("powershell.exe", "-noprofile -command \"Get-Clipboard\""); - output.WriteLine ($" WSL: powershell.exe Get-Clipboard: exitCode = {exitCode}, result = {clipReadText}"); - if (exitCode == 0) { - Application.RequestStop (); - return; - } - failed = true; - } - - if (failed = xclipExists () == false) { - // xclip doesn't exist then exit. - Application.RequestStop (); - return; - } - - (exitCode, clipReadText) = ClipboardProcessRunner.Process ("bash", $"-c \"xclip -sel clip -o\""); - output.WriteLine ($" Linux: bash xclip -sel clip -o: exitCode = {exitCode}, result = {clipReadText}"); - Assert.Equal (0, exitCode); - } - - Application.RequestStop (); - }; - - Application.Run (); - - if (!failed) Assert.Equal (clipText, clipReadText.TrimEnd ()); - - } -#endif - - bool Is_WSL_Platform () - { - (int _, string result) = ClipboardProcessRunner.Process ("bash", $"-c \"uname -a\""); - - return result.Contains ("microsoft") && result.Contains ("WSL"); - } - - bool xclipExists () - { - try - { - (int _, string result) = ClipboardProcessRunner.Process ("bash", $"-c \"which xclip\""); - - return result.TrimEnd () != ""; - } - catch (Exception) - { - return false; - } - } -} -#endif diff --git a/Tests/UnitTests/Configuration/AppScopeTests.cs b/Tests/UnitTests/Configuration/AppScopeTests.cs deleted file mode 100644 index df40886603..0000000000 --- a/Tests/UnitTests/Configuration/AppScopeTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -#nullable enable -using System.Text.Json; -using UnitTests; -using static Terminal.Gui.Configuration.ConfigurationManager; - -namespace UnitTests.ConfigurationTests; - -public class AppSettingsScopeTests -{ - [Fact] - public void Empty_By_Default_Disabled () - { - Assert.False (IsEnabled); - - Assert.NotNull (Settings! ["AppSettings"].PropertyValue); - - AppSettingsScope? appSettings = (Settings! ["AppSettings"].PropertyValue as AppSettingsScope); - Assert.Equal (10, appSettings!.Count); // 10 test properties - - Assert.Equal ("test", (((AppSettingsScope)Settings! ["AppSettings"].PropertyValue!)!) ["AppSettingsTestClass.ReferenceProperty"].PropertyValue); - } - - [Fact] - public void Empty_By_Default_Enabled () - { - Enable (ConfigLocations.HardCoded); - - Assert.NotNull (Settings! ["AppSettings"].PropertyValue); - - AppSettingsScope? appSettings = (Settings! ["AppSettings"].PropertyValue as AppSettingsScope); - Assert.Equal (10, appSettings!.Count); // 10 test properties - Assert.Equal ("test", (((AppSettingsScope)Settings! ["AppSettings"].PropertyValue!)!) ["AppSettingsTestClass.ReferenceProperty"].PropertyValue); - - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void Apply_ShouldApplyUpdatedProperties () - { - Enable (ConfigLocations.HardCoded); - - Assert.Null (AppSettingsTestClass.NullableValueProperty); - Assert.NotEmpty (AppSettings!); - Assert.Null (AppSettings! ["AppSettingsTestClass.NullableValueProperty"].PropertyValue); - - AppSettingsTestClass.NullableValueProperty = true; - UpdateToCurrentValues (); - Assert.True (AppSettingsTestClass.NullableValueProperty); - Assert.NotEmpty (AppSettings); - Assert.True (AppSettings ["AppSettingsTestClass.NullableValueProperty"].PropertyValue as bool?); - - AppSettings ["AppSettingsTestClass.NullableValueProperty"].PropertyValue = false; - Assert.False (AppSettings ["AppSettingsTestClass.NullableValueProperty"].PropertyValue as bool?); - - // ConfigurationManager.Settings should NOT apply theme settings - Settings!.Apply (); - Assert.True (AppSettingsTestClass.NullableValueProperty); - - // ConfigurationManager.Themes should NOT apply theme settings - ThemeManager.Themes! [ThemeManager.Theme]!.Apply (); - Assert.True (AppSettingsTestClass.NullableValueProperty); - - // ConfigurationManager.AppSettings should NOT apply theme settings - AppSettings.Apply (); - Assert.False (AppSettingsTestClass.NullableValueProperty); - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void TestNullable () - { - Enable (ConfigLocations.HardCoded); - - AppSettingsTestClass.NullableValueProperty = null; - Assert.Null (AppSettingsTestClass.NullableValueProperty); - - ResetToHardCodedDefaults (); - Assert.Null (AppSettings! ["AppSettingsTestClass.NullableValueProperty"].PropertyValue); - - Apply (); - Assert.Null (AppSettings! ["AppSettingsTestClass.NullableValueProperty"].PropertyValue); - Assert.Null (AppSettingsTestClass.NullableValueProperty); - - AppSettingsTestClass.NullableValueProperty = true; - UpdateToCurrentValues (); - Assert.True ((bool)AppSettings! ["AppSettingsTestClass.NullableValueProperty"].PropertyValue!); - Assert.True (AppSettingsTestClass.NullableValueProperty); - Assert.NotNull (AppSettingsTestClass.NullableValueProperty); - Apply (); - Assert.True (AppSettingsTestClass.NullableValueProperty); - Assert.NotNull (AppSettingsTestClass.NullableValueProperty); - - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void TestSerialize_RoundTrip () - { - Enable (ConfigLocations.HardCoded); - - AppSettingsScope initial = AppSettings!; - - string serialized = JsonSerializer.Serialize (AppSettings, SerializerContext.Options); - var deserialized = JsonSerializer.Deserialize (serialized, SerializerContext.Options); - - Assert.NotEqual (initial, deserialized); - Assert.Equal (deserialized!.Count, initial.Count); - - Disable (resetToHardCodedDefaults: true); - } - - public class AppSettingsTestClass - { - [ConfigurationProperty (Scope = typeof (AppSettingsScope))] - public static bool ValueProperty { get; set; } - - [ConfigurationProperty (Scope = typeof (AppSettingsScope))] - public static bool? NullableValueProperty { get; set; } - - [ConfigurationProperty (Scope = typeof (AppSettingsScope))] - public static string ReferenceProperty { get; set; } = "test"; - - [ConfigurationProperty (Scope = typeof (AppSettingsScope))] - public static string? NullableReferenceProperty { get; set; } - } -} diff --git a/Tests/UnitTests/Configuration/ConfigPropertyTests.cs b/Tests/UnitTests/Configuration/ConfigPropertyTests.cs deleted file mode 100644 index b516291da9..0000000000 --- a/Tests/UnitTests/Configuration/ConfigPropertyTests.cs +++ /dev/null @@ -1,468 +0,0 @@ -#nullable enable -using System.Collections.Concurrent; -using System.Reflection; -using System.Text.Json; - -namespace UnitTests.ConfigurationTests; - -public class ConfigPropertyTests -{ - [Fact] - public void Apply_PropertyValueIsAppliedToStatic_String_Property () - { - // Arrange - TestConfiguration.Reset (); - var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = "UpdatedValue" - }; - - // Act - var result = configProperty.Apply (); - - // Assert - Assert.Equal (1, TestConfiguration.TestStringPropertySetCount); - Assert.True (result); - Assert.Equal ("UpdatedValue", TestConfiguration.TestStringProperty); - TestConfiguration.Reset (); - } - - [Fact] - public void Apply_PropertyValueIsAppliedToStatic_Key_Property () - { - // Arrange - TestConfiguration.Reset (); - var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestKeyProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = Key.Q.WithCtrl - }; - - // Act - var result = configProperty.Apply (); - - // Assert - Assert.Equal (1, TestConfiguration.TestKeyPropertySetCount); - Assert.True (result); - Assert.Equal (Key.Q.WithCtrl, TestConfiguration.TestKeyProperty); - TestConfiguration.Reset (); - } - - [Fact] - public void RetrieveValue_GetsCurrentValueOfStaticProperty () - { - // Arrange - TestConfiguration.TestStringProperty = "CurrentValue"; - var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo - }; - - // Act - var value = configProperty.UpdateToCurrentValue (); - - // Assert - Assert.Equal ("CurrentValue", value); - Assert.Equal ("CurrentValue", configProperty.PropertyValue); - } - - [Fact] - public void DeepCloneFrom_Updates_String_Property_Value () - { - // Arrange - TestConfiguration.Reset (); - var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = "InitialValue" - }; - - // Act - var updatedValue = configProperty.UpdateFrom ("NewValue"); - - // Assert - Assert.Equal (0, TestConfiguration.TestStringPropertySetCount); - Assert.Equal ("NewValue", updatedValue); - Assert.Equal ("NewValue", configProperty.PropertyValue); - TestConfiguration.Reset (); - } - - //[Fact] - //public void UpdateValueFrom_InvalidType_ThrowsArgumentException() - //{ - // // Arrange - // var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - // var configProperty = new ConfigProperty - // { - // PropertyInfo = propertyInfo - // }; - - // // Act & Assert - // Assert.Throws(() => configProperty.UpdateValueFrom(123)); - //} - - [Fact] - public void Apply_TargetInvocationException_ThrowsJsonException () - { - // Arrange - var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = null // This will cause ArgumentNullException in the set accessor - }; - - // Act & Assert - var exception = Assert.Throws (() => configProperty.Apply ()); - } - - [Fact] - public void GetJsonPropertyName_ReturnsJsonPropertyNameAttributeValue () - { - // Arrange - var propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestStringProperty)); - - // Act - var jsonPropertyName = ConfigProperty.GetJsonPropertyName (propertyInfo!); - - // Assert - Assert.Equal ("TestStringProperty", jsonPropertyName); - } - [Fact] - public void UpdateFrom_NullSource_ReturnsExistingValue() - { - // Arrange - TestConfiguration.TestStringProperty = "CurrentValue"; - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = "ExistingValue" - }; - - // Act - var result = configProperty.UpdateFrom(null); - - // Assert - Assert.Equal("ExistingValue", result); - Assert.Equal("ExistingValue", configProperty.PropertyValue); - } - - [Fact] - public void UpdateFrom_InvalidType_ThrowsArgumentException() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = "ExistingValue" - }; - - // Act & Assert - Assert.Throws(() => configProperty.UpdateFrom(123)); - } - - [Fact] - public void UpdateFrom_ConfigPropertySource_CopiesValue() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = "ExistingValue" - }; - - var sourceConfigProperty = new ConfigProperty - { - PropertyValue = "SourceValue", - HasValue = true - }; - - // Act - var result = configProperty.UpdateFrom(sourceConfigProperty); - - // Assert - Assert.Equal("SourceValue", result); - Assert.Equal("SourceValue", configProperty.PropertyValue); - } - - [Fact] - public void UpdateFrom_ConfigPropertySource_WithoutValue_KeepsExistingValue() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = "ExistingValue" - }; - - var sourceConfigProperty = new ConfigProperty - { - HasValue = false - }; - - // Act - var result = configProperty.UpdateFrom(sourceConfigProperty); - - // Assert - Assert.Equal("ExistingValue", result); - Assert.Equal("ExistingValue", configProperty.PropertyValue); - } - - [Fact] - public void UpdateFrom_ConcurrentDictionaryOfThemeScopes_UpdatesValues() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestDictionaryProperty)); - - // Create a destination dictionary - var destinationDict = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - destinationDict.TryAdd("theme1", new ThemeScope()); - - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = destinationDict - }; - - // Create a source dictionary with one matching key and one new key - var sourceDict = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - var sourceTheme1 = new ThemeScope(); - var sourceTheme2 = new ThemeScope(); - - // Add a property to sourceTheme1 to verify it gets updated - var keyProperty = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestKeyProperty)); - if (sourceTheme1.TryAdd("TestKey", new ConfigProperty { PropertyInfo = keyProperty, PropertyValue = Key.A.WithCtrl, HasValue = true })) - { - // Successfully added - } - - sourceDict.TryAdd("theme1", sourceTheme1); - sourceDict.TryAdd("theme2", sourceTheme2); - - // Act - var result = configProperty.UpdateFrom(sourceDict); - - // Assert - var resultDict = result as ConcurrentDictionary; - Assert.NotNull(resultDict); - Assert.Equal(2, resultDict.Count); - Assert.True(resultDict.ContainsKey("theme1")); - Assert.True(resultDict.ContainsKey("theme2")); - - // Verify the theme1 was updated with the property - Assert.True(resultDict["theme1"].ContainsKey("TestKey")); - Assert.Equal(Key.A.WithCtrl, resultDict["theme1"]["TestKey"].PropertyValue); - } - - [Fact] - public void UpdateFrom_ConcurrentDictionaryOfConfigProperties_UpdatesValues() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestConfigDictionaryProperty)); - - // Create a destination dictionary - var destinationDict = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - destinationDict.TryAdd("prop1", new ConfigProperty { PropertyValue = "Original", HasValue = true }); - - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = destinationDict - }; - - // Create a source dictionary with one matching key and one new key - var sourceDict = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - sourceDict.TryAdd("prop1", new ConfigProperty { PropertyValue = "Updated", HasValue = true }); - sourceDict.TryAdd("prop2", new ConfigProperty { PropertyValue = "New", HasValue = true }); - - // Act - var result = configProperty.UpdateFrom(sourceDict); - - // Assert - var resultDict = result as ConcurrentDictionary; - Assert.NotNull(resultDict); - Assert.Equal(2, resultDict.Count); - Assert.True(resultDict.ContainsKey("prop1")); - Assert.True(resultDict.ContainsKey("prop2")); - Assert.Equal("Updated", resultDict["prop1"].PropertyValue); - Assert.Equal("New", resultDict["prop2"].PropertyValue); - } - - [Fact] - public void UpdateFrom_DictionaryOfConfigProperties_UpdatesValues() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestConfigDictionaryProperty)); - - // Create a destination dictionary - var destinationDict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - destinationDict.Add("prop1", new ConfigProperty { PropertyValue = "Original", HasValue = true }); - - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - PropertyValue = destinationDict - }; - - // Create a source dictionary with one matching key and one new key - var sourceDict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - sourceDict.Add("prop1", new ConfigProperty { PropertyValue = "Updated", HasValue = true }); - sourceDict.Add("prop2", new ConfigProperty { PropertyValue = "New", HasValue = true }); - - // Act - var result = configProperty.UpdateFrom(sourceDict); - - // Assert - var resultDict = result as Dictionary; - Assert.NotNull(resultDict); - Assert.Equal(2, resultDict.Count); - Assert.True(resultDict.ContainsKey("prop1")); - Assert.True(resultDict.ContainsKey("prop2")); - Assert.Equal("Updated", resultDict["prop1"].PropertyValue); - Assert.Equal("New", resultDict["prop2"].PropertyValue); - } - - [Fact] - public void PropertyValue_SetWhenImmutable_ThrowsException() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo, - Immutable = true - }; - - // Act & Assert - var exception = Assert.Throws(() => configProperty.PropertyValue = "New Value"); - Assert.Contains("immutable", exception.Message); - } - - [Fact] - public void CreateWithAttributeInfo_ReturnsConfigPropertyWithCorrectValues() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - - // Act - var configProperty = ConfigProperty.CreateImmutableWithAttributeInfo(propertyInfo!); - - // Assert - Assert.Equal(propertyInfo, configProperty.PropertyInfo); - Assert.False(configProperty.OmitClassName); - Assert.True(configProperty.Immutable); - Assert.Null(configProperty.PropertyValue); - Assert.False(configProperty.HasValue); - } - - [Fact] - public void HasConfigurationPropertyAttribute_ReturnsTrue_ForDecoratedProperty() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringProperty)); - - // Act - var result = ConfigProperty.HasConfigurationPropertyAttribute(propertyInfo!); - - // Assert - Assert.True(result); - } - - [Fact] - public void HasConfigurationPropertyAttribute_ReturnsFalse_ForNonDecoratedProperty() - { - // Arrange - var propertyInfo = typeof(TestConfiguration).GetProperty(nameof(TestConfiguration.TestStringPropertySetCount)); - - // Act - var result = ConfigProperty.HasConfigurationPropertyAttribute(propertyInfo!); - - // Assert - Assert.False(result); - } - public class TestConfiguration - { - private static string _testStringProperty = "Default"; - public static int TestStringPropertySetCount { get; set; } - - [ConfigurationProperty] - public static string TestStringProperty - { - get => _testStringProperty; - set - { - TestStringPropertySetCount++; - _testStringProperty = value ?? throw new ArgumentNullException (nameof (value)); - } - } - - private static Key _testKeyProperty = Key.Esc; - public static int TestKeyPropertySetCount { get; set; } - - [ConfigurationProperty] - public static Key TestKeyProperty - { - get => _testKeyProperty; - set - { - TestKeyPropertySetCount++; - _testKeyProperty = value ?? throw new ArgumentNullException (nameof (value)); - } - } - - // Add these new properties for testing dictionaries - [ConfigurationProperty] - public static ConcurrentDictionary? TestDictionaryProperty { get; set; } - - [ConfigurationProperty] - public static Dictionary? TestRegularDictionaryProperty { get; set; } - - [ConfigurationProperty] - public static ConcurrentDictionary? TestConfigDictionaryProperty { get; set; } - - [ConfigurationProperty] - public static Scheme? TestSchemeProperty { get; set; } - - - public static void Reset () - { - TestStringPropertySetCount = 0; - TestKeyPropertySetCount = 0; - TestDictionaryProperty = null; - TestRegularDictionaryProperty = null; - TestConfigDictionaryProperty = null; - TestSchemeProperty = null; - } - } - - [Fact] - public void UpdateFrom_SchemeSource_UpdatesValue () - { - // Arrange - PropertyInfo? propertyInfo = typeof (TestConfiguration).GetProperty (nameof (TestConfiguration.TestSchemeProperty)); - Scheme sourceScheme = new (new Attribute (Color.Red, Color.Blue, TextStyle.Bold)); - - var configProperty = new ConfigProperty - { - PropertyInfo = propertyInfo!, - PropertyValue = new Scheme (new Attribute (Color.White, Color.Black, TextStyle.None)) - }; - - // Act - object? result = configProperty.UpdateFrom (sourceScheme); - - // Assert - Assert.Equal (sourceScheme, result); - Assert.Equal (sourceScheme, configProperty.PropertyValue); - Assert.NotSame (sourceScheme, configProperty.PropertyValue); // Prove it's a clone, not a ref - } -} diff --git a/Tests/UnitTests/Configuration/KeyJsonConverterTests.cs b/Tests/UnitTests/Configuration/KeyJsonConverterTests.cs deleted file mode 100644 index 8f3e6f973a..0000000000 --- a/Tests/UnitTests/Configuration/KeyJsonConverterTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Text; -using System.Text.Encodings.Web; -using System.Text.Json; -using System.Text.Unicode; - -namespace UnitTests.ConfigurationTests; - -public class KeyJsonConverterTests -{ - - [Fact] - public void Separator_Property_Set_Changes_Serialization_Format () - { - Rune savedSeparator = Key.Separator; - - // Act - // NOTE: This means this test can't be parallelized - Key.Separator = (Rune)'*'; - string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager.SerializerContext.Options); - - // Assert - Assert.Equal ("\"*\"", json); - Key.Separator = savedSeparator; - } - - [Theory] - [InlineData ('A', '+', "\"Ctrl+Alt+A\"")] - [InlineData ('A', '-', "\"Ctrl+Alt+A\"")] - [InlineData ('A', '*', "\"Ctrl+Alt+A\"")] - [InlineData ('A', '@', "\"Ctrl+Alt+A\"")] - [InlineData ('A', '+', "\"Ctrl@Alt@A\"")] - [InlineData ('A', '-', "\"Ctrl@Alt@A\"")] - [InlineData ('A', '*', "\"Ctrl@Alt@A\"")] - [InlineData ('A', '@', "\"Ctrl@Alt@A\"")] - [InlineData ('+', '+', "\"Ctrl+Alt++\"")] - [InlineData ('+', '-', "\"Ctrl+Alt++\"")] - [InlineData ('+', '*', "\"Ctrl+Alt++\"")] - [InlineData ('+', '@', "\"Ctrl+Alt++\"")] - [InlineData ('+', '+', "\"Ctrl@Alt@+\"")] - [InlineData ('+', '-', "\"Ctrl@Alt@+\"")] - [InlineData ('+', '*', "\"Ctrl@Alt@+\"")] - [InlineData ('+', '@', "\"Ctrl@Alt@+\"")] - public void Separator_Property_Set_Deserialization_Works_With_Any (char keyChar, char separator, string json) - { - Rune savedSeparator = Key.Separator; - - // Act - // NOTE: This means this test can't be parallelized - Key.Separator = (Rune)separator; - - Key deserializedKey = JsonSerializer.Deserialize (json, ConfigurationManager.SerializerContext.Options); - - Key expectedKey = new Key ((KeyCode)keyChar).WithCtrl.WithAlt; - // Assert - Assert.Equal (expectedKey, deserializedKey); - Key.Separator = savedSeparator; - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Configuration/MemorySizeEstimator.cs b/Tests/UnitTests/Configuration/MemorySizeEstimator.cs deleted file mode 100644 index 7562650d2b..0000000000 --- a/Tests/UnitTests/Configuration/MemorySizeEstimator.cs +++ /dev/null @@ -1,253 +0,0 @@ -#nullable enable - -namespace UnitTests.ConfigurationTests; - -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -public static class MemorySizeEstimator -{ - public static long EstimateSize (T? source) - { - if (source is null) - { - return 0; - } - - ConcurrentDictionary visited = new (ReferenceEqualityComparer.Instance); - return EstimateSizeInternal (source, visited); - } - - private const int POINTER_SIZE = 8; // 64-bit system - private const int OBJECT_HEADER_SIZE = 16; // 2 pointers for GC - - private static long EstimateSizeInternal (object? source, ConcurrentDictionary visited) - { - if (source is null) - { - return 0; - } - - // Handle already visited objects to avoid circular references - if (visited.TryGetValue (source, out long existingSize)) - { - // // Log revisited object (enable for debugging) - // Console.WriteLine($"Revisited {source.GetType().FullName}: {existingSize} bytes"); - return existingSize; - } - - Type type = source.GetType (); - long size = 0; - - // Handle simple types - if (IsSimpleType (type)) - { - size = EstimateSimpleTypeSize (source, type); - visited.TryAdd (source, size); - // // Log simple type (enable for debugging) - // Console.WriteLine($"{type.FullName}: {size} bytes"); - return size; - } - - // Handle arrays - if (type.IsArray) - { - size = EstimateArraySize (source, visited); - } - // Handle dictionaries - else if (source is IDictionary) - { - size = EstimateDictionarySize (source, visited); - } - // Handle collections - else if (typeof (ICollection).IsAssignableFrom (type)) - { - size = EstimateCollectionSize (source, visited); - } - // Handle structs and classes - else - { - size = EstimateObjectSize (source, type, visited); - } - - visited.TryAdd (source, size); - // // Log object size (enable for debugging) - // if (size == 0) - // { - // Console.WriteLine($"Zero size for {type.FullName}"); - // } - // else - // { - // Console.WriteLine($"{type.FullName}: {size} bytes"); - // } - - return size; - } - - private static bool IsSimpleType (Type type) - { - if (type.IsPrimitive - || type.IsEnum - || type == typeof (decimal) - || type == typeof (DateTime) - || type == typeof (DateTimeOffset) - || type == typeof (TimeSpan) - || type == typeof (Guid) - || type == typeof (Rune) - || type == typeof (string)) - { - return true; - } - - // Treat structs with no writable public properties as simple types - if (type.IsValueType) - { - PropertyInfo [] writableProperties = type.GetProperties (BindingFlags.Instance | BindingFlags.Public) - .Where (p => p is { CanRead: true, CanWrite: true } && p.GetIndexParameters ().Length == 0) - .ToArray (); - return writableProperties.Length == 0; - } - - // Treat Property翰Info as simple (metadata, not cloned) - if (typeof (PropertyInfo).IsAssignableFrom (type)) - { - return true; - } - - return false; - } - - private static long EstimateSimpleTypeSize (object source, Type type) - { - if (type == typeof (string)) - { - string str = (string)source; - // Header + length (4) + char array ref + chars (2 bytes each) - return OBJECT_HEADER_SIZE + 4 + POINTER_SIZE + (str.Length * 2); - } - - try - { - return Marshal.SizeOf (type); - } - catch (ArgumentException) - { - // Fallback for enums or other simple types - return 4; // Conservative estimate - } - } - - private static long EstimateArraySize (object source, ConcurrentDictionary visited) - { - Array array = (Array)source; - long size = OBJECT_HEADER_SIZE + 4 + POINTER_SIZE; // Header + length + padding - - foreach (object? element in array) - { - size += EstimateSizeInternal (element, visited); - } - - return size; - } - - private static long EstimateDictionarySize (object source, ConcurrentDictionary visited) - { - IDictionary dict = (IDictionary)source; - long size = OBJECT_HEADER_SIZE + (POINTER_SIZE * 5); // Header + buckets, entries, comparer, fields - size += dict.Count * 4; // Bucket array (~4 bytes per entry) - size += dict.Count * (4 + 4 + POINTER_SIZE * 2); // Entry array: hashcode, next, key, value - - foreach (object? key in dict.Keys) - { - size += EstimateSizeInternal (key, visited); - size += EstimateSizeInternal (dict [key], visited); - } - - return size; - } - - private static long EstimateCollectionSize (object source, ConcurrentDictionary visited) - { - Type type = source.GetType (); - long size = OBJECT_HEADER_SIZE + (POINTER_SIZE * 3); // Header + internal array + fields - - if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Dictionary<,>)) - { - return EstimateDictionarySize (source, visited); - } - - if (source is IEnumerable enumerable) - { - foreach (object? item in enumerable) - { - size += EstimateSizeInternal (item, visited); - } - } - - return size; - } - - private static long EstimateObjectSize (object source, Type type, ConcurrentDictionary visited) - { - long size = OBJECT_HEADER_SIZE; - - // Size public writable properties - foreach (PropertyInfo prop in type.GetProperties (BindingFlags.Instance | BindingFlags.Public) - .Where (p => p is { CanRead: true, CanWrite: true } && p.GetIndexParameters ().Length == 0)) - { - try - { - object? value = prop.GetValue (source); - size += EstimateSizeInternal (value, visited); - } - catch (Exception) - { - // // Log exception (enable for debugging) - // Console.WriteLine($"Error processing property {prop.Name} of {type.FullName}: {ex.Message}"); - // Continue to avoid crashing - } - } - - // For structs, also size fields (to handle generic structs) - if (type.IsValueType) - { - FieldInfo [] fields = type.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - foreach (FieldInfo field in fields) - { - try - { - object? fieldValue = field.GetValue (source); - size += EstimateSizeInternal (fieldValue, visited); - } - catch (Exception) - { - // // Log exception (enable for debugging) - // Console.WriteLine($"Error processing field {field.Name} of {type.FullName}: {ex.Message}"); - // Continue to avoid crashing - } - } - } - - return size; - } - - private sealed class ReferenceEqualityComparer : IEqualityComparer - { - public static ReferenceEqualityComparer Instance { get; } = new (); - - public new bool Equals (object? x, object? y) - { - return ReferenceEquals (x, y); - } - - public int GetHashCode (object obj) - { - return RuntimeHelpers.GetHashCode (obj); - } - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Configuration/SchemeManagerTests.cs b/Tests/UnitTests/Configuration/SchemeManagerTests.cs deleted file mode 100644 index 2d52dcb3ef..0000000000 --- a/Tests/UnitTests/Configuration/SchemeManagerTests.cs +++ /dev/null @@ -1,986 +0,0 @@ -#nullable enable -using System.Collections.Frozen; -using System.Collections.Immutable; -using System.Text.Json; -using static Terminal.Gui.Configuration.ConfigurationManager; - -namespace UnitTests.ConfigurationTests; - -public class SchemeManagerTests -{ - [Fact] - public void GetSchemes_Not_Enabled_Gets_Schemes () - { - Disable (true); - - Dictionary schemes = SchemeManager.GetSchemesForCurrentTheme (); - Assert.NotNull (schemes); - Assert.NotNull (schemes ["Base"]); - Assert.True (schemes!.ContainsKey ("Base")); - Assert.True (schemes.ContainsKey ("base")); - - Assert.Equal (SchemeManager.GetSchemes (), schemes); - } - - [Fact] - public void GetSchemes_Enabled_Gets_Current () - { - try - { - Enable (ConfigLocations.HardCoded); - - Dictionary schemes = SchemeManager.GetSchemesForCurrentTheme (); - Assert.NotNull (schemes); - Assert.NotNull (schemes ["Base"]); - Assert.True (schemes.ContainsKey ("Base")); - Assert.True (schemes.ContainsKey ("base")); - - Assert.Equal (SchemeManager.GetSchemes (), schemes); - } - finally - { - Disable (true); - } - } - - [Fact] - public void GetSchemes_Get_Schemes_After_Load () - { - try - { - Enable (ConfigLocations.HardCoded); - Load (ConfigLocations.All); - Apply (); - - Assert.Equal (SchemeManager.GetSchemes (), SchemeManager.GetSchemesForCurrentTheme ()); - } - finally - { - Disable (true); - } - } - - [Fact] - public void GetHardCodedSchemes_Gets_HardCoded_Theme_Schemes () - { - ImmutableSortedDictionary? hardCoded = SchemeManager.GetHardCodedSchemes (); - - Assert.Equal (Scheme.GetHardCodedSchemes (), hardCoded!); - } - - [Fact] - public void GetHardCodedSchemes_Have_Expected_Normal_Attributes () - { - ImmutableSortedDictionary? schemes = SchemeManager.GetHardCodedSchemes (); - Assert.NotNull (schemes); - - // Base - Scheme? baseScheme = schemes ["Base"]; - Assert.NotNull (baseScheme); - Assert.Equal (new Attribute (Color.None, Color.None), baseScheme.Normal); - - // Dialog - Scheme? dialogScheme = schemes ["Dialog"]; - Assert.NotNull (dialogScheme); - Assert.Equal (new Attribute (StandardColor.LightSkyBlue, StandardColor.OuterSpace), dialogScheme.Normal); - - // Error - Scheme? errorScheme = schemes ["Error"]; - Assert.NotNull (errorScheme); - Assert.Equal (new Attribute (StandardColor.IndianRed, StandardColor.RaisinBlack), errorScheme.Normal); - - // Menu (Bold style) - Scheme? menuScheme = schemes ["Menu"]; - Assert.NotNull (menuScheme); - Assert.Equal (new Attribute (StandardColor.Charcoal, StandardColor.LightBlue, TextStyle.Bold), menuScheme.Normal); - - // Runnable (uses Color.None for transparent background) - Scheme? runnableScheme = schemes ["Runnable"]; - Assert.NotNull (runnableScheme); - Assert.Equal (new Attribute (Color.None, Color.None), runnableScheme.Normal); - } - - [Fact] - public void GetHardCodedSchemes_Have_Expected_Normal_Attributes_LoadHardCodedDefaults () - { - LoadHardCodedDefaults (); - ImmutableSortedDictionary? schemes = SchemeManager.GetHardCodedSchemes (); - - Assert.NotNull (schemes); - - // Base - Scheme? baseScheme = schemes ["Base"]; - Assert.NotNull (baseScheme); - Assert.Equal (new Attribute (Color.None, Color.None), baseScheme.Normal); - - // Dialog - Scheme? dialogScheme = schemes ["Dialog"]; - Assert.NotNull (dialogScheme); - Assert.Equal (new Attribute (StandardColor.LightSkyBlue, StandardColor.OuterSpace), dialogScheme.Normal); - - // Error - Scheme? errorScheme = schemes ["Error"]; - Assert.NotNull (errorScheme); - Assert.Equal (new Attribute (StandardColor.IndianRed, StandardColor.RaisinBlack), errorScheme.Normal); - - // Menu (Bold style) - Scheme? menuScheme = schemes ["Menu"]; - Assert.NotNull (menuScheme); - Assert.Equal (new Attribute (StandardColor.Charcoal, StandardColor.LightBlue, TextStyle.Bold), menuScheme.Normal); - - // Runnable (uses Color.None for transparent background) - Scheme? runnableScheme = schemes ["Runnable"]; - Assert.NotNull (runnableScheme); - Assert.Equal (new Attribute (Color.None, Color.None), runnableScheme.Normal); - } - - [Fact] - public void Not_Case_Sensitive_Disabled () - { - Assert.False (IsEnabled); - Dictionary current = SchemeManager.GetSchemesForCurrentTheme (); - Assert.NotNull (current); - - Assert.True (current.ContainsKey ("Base")); - Assert.True (current.ContainsKey ("base")); - } - - [Fact] - public void Not_Case_Sensitive_Enabled () - { - Assert.False (IsEnabled); - - try - { - Enable (ConfigLocations.HardCoded); - - Assert.True (SchemeManager.GetSchemesForCurrentTheme ().ContainsKey ("Base")); - Assert.True (SchemeManager.GetSchemesForCurrentTheme ().ContainsKey ("base")); - - ResetToHardCodedDefaults (); - Dictionary current = SchemeManager.GetSchemesForCurrentTheme (); - Assert.NotNull (current); - - Assert.True (current.ContainsKey ("Base")); - Assert.True (current.ContainsKey ("base")); - } - finally - { - Disable (true); - } - } - - [Fact] - public void Load_Adds () - { - // arrange - Enable (ConfigLocations.HardCoded); - - var theme = new ThemeScope (); - theme.LoadHardCodedDefaults (); - Assert.NotEmpty (theme); - - Assert.Equal (5, SchemeManager.GetSchemes ().Count); - - theme ["Schemes"].PropertyValue = SchemeManager.GetSchemes (); - - Dictionary schemes = (Dictionary)theme ["Schemes"].PropertyValue!; - Assert.Equal (SchemeManager.GetSchemes ().Count, schemes.Count); - - var newTheme = new ThemeScope (); - newTheme.LoadHardCodedDefaults (); - - var scheme = new Scheme - { - // note: Scheme's can't be partial; default for each attribute - // is always White/Black - Normal = new Attribute (Color.Red, Color.Green), - Focus = new Attribute (Color.Cyan, Color.BrightCyan), - HotNormal = new Attribute (Color.Yellow, Color.BrightYellow), - HotFocus = new Attribute (Color.Green, Color.BrightGreen), - Disabled = new Attribute (Color.Gray, Color.DarkGray) - }; - - newTheme ["Schemes"].PropertyValue = SchemeManager.GetSchemesForCurrentTheme (); - Assert.Equal (5, SchemeManager.GetSchemes ().Count); - - // add a new Scheme to the newTheme - ((Dictionary)theme ["Schemes"].PropertyValue!) ["Test"] = scheme; - - schemes = (Dictionary)theme ["Schemes"].PropertyValue!; - Assert.Equal (SchemeManager.GetSchemes ().Count, schemes.Count); - - // Act - theme.UpdateFrom (newTheme); - - // Assert - schemes = (Dictionary)theme ["Schemes"].PropertyValue!; - Assert.Equal (schemes ["Test"].Normal, scheme.Normal); - Assert.Equal (schemes ["Test"].Focus, scheme.Focus); - Disable (true); - } - - [Fact] - public void Load_Changes () - { - // arrange - Enable (ConfigLocations.HardCoded); - - var theme = new ThemeScope (); - theme.LoadHardCodedDefaults (); - Assert.NotEmpty (theme); - - var scheme = new Scheme - { - // note: Scheme's can't be partial; default for each attribute - // is always White/Black - Normal = new Attribute (Color.Red, Color.Green), - Focus = new Attribute (Color.Cyan, Color.BrightCyan), - HotNormal = new Attribute (Color.Yellow, Color.BrightYellow), - HotFocus = new Attribute (Color.Green, Color.BrightGreen), - Disabled = new Attribute (Color.Gray, Color.DarkGray) - }; - theme ["Schemes"].PropertyValue = SchemeManager.GetSchemesForCurrentTheme (); - - ((Dictionary)theme ["Schemes"].PropertyValue!) ["Test"] = scheme; - - Dictionary schemes = (Dictionary)theme ["Schemes"].PropertyValue!; - Assert.Equal (scheme.Normal, schemes ["Test"].Normal); - Assert.Equal (scheme.Focus, schemes ["Test"].Focus); - - // Change just Normal - var newTheme = new ThemeScope (); - newTheme.LoadHardCodedDefaults (); - - var newScheme = new Scheme - { - Normal = new Attribute (Color.Blue, Color.BrightBlue), - Focus = scheme.Focus, - HotNormal = scheme.HotNormal, - HotFocus = scheme.HotFocus, - Disabled = scheme.Disabled - }; - newTheme ["Schemes"].PropertyValue = SchemeManager.GetSchemesForCurrentTheme (); - ((Dictionary)newTheme ["Schemes"].PropertyValue!) ["Test"] = newScheme; - - // Act - theme.UpdateFrom (newTheme); - - // Assert - schemes = (Dictionary)theme ["Schemes"].PropertyValue!; - - // Normal should have changed - Assert.Equal (new Attribute (Color.Blue, Color.BrightBlue), schemes ["Test"].Normal); - Assert.Equal (Color.BrightBlue, schemes ["Test"].Normal.Background); - Assert.Equal (Color.Cyan, schemes ["Test"].Focus.Foreground); - Assert.Equal (Color.BrightCyan, schemes ["Test"].Focus.Background); - Disable (true); - } - - [Fact (Skip = "TODO: This should throw an exception")] - public void Load_Null_Scheme_Throws () - { - try - { - Enable (ConfigLocations.HardCoded); - ThrowOnJsonErrors = true; - - // Create a test theme - RuntimeConfig = """ - { - "Theme": "TestTheme", - "Themes": [ - { - "TestTheme": { - } - } - ] - } - """; - - // Load the test theme - // TODO: This should throw an exception! - Assert.Throws (() => Load (ConfigLocations.Runtime)); - Assert.Contains ("TestTheme", ThemeManager.Themes!); - Assert.Equal ("TestTheme", ThemeManager.Theme); - Assert.Throws (SchemeManager.GetSchemes); - } - finally - { - Disable (true); - } - } - - [Fact] - public void Load_Empty_Scheme_Throws () - { - try - { - Enable (ConfigLocations.HardCoded); - ThrowOnJsonErrors = true; - - // Create a test theme - RuntimeConfig = """ - { - "Theme": "TestTheme", - "Themes": [ - { - "TestTheme": { - "Schemes": [] - } - } - ] - } - """; - - // Load the test theme - Load (ConfigLocations.Runtime); - Assert.Equal ("TestTheme", ThemeManager.Theme); - - // Now reset everything and reload - ResetToHardCodedDefaults (); - - // Verify we're back to default - Assert.Equal ("Default", ThemeManager.Theme); - } - finally - { - Disable (true); - } - } - - [Fact] - public void Load_Custom_Scheme_Loads () - { - try - { - Enable (ConfigLocations.HardCoded); - ThrowOnJsonErrors = true; - - // Create a test theme - RuntimeConfig = """ - { - "Theme": "TestTheme", - "Themes": [ - { - "TestTheme": { - "Schemes": [ - { - "Runnable": { - "Normal": { - "Foreground": "AntiqueWhite", - "Background": "DimGray" - }, - "Focus": { - "Foreground": "White", - "Background": "DarkGray" - }, - "HotNormal": { - "Foreground": "Wheat", - "Background": "DarkGray", - "Style": "Underline" - }, - "HotFocus": { - "Foreground": "LightYellow", - "Background": "DimGray", - "Style": "Underline" - }, - "Disabled": { - "Foreground": "Black", - "Background": "DimGray" - } - } - }, - { - "Base": { - "Normal": { - "Foreground": "White", - "Background": "Blue" - }, - "Focus": { - "Foreground": "DarkBlue", - "Background": "LightGray" - }, - "HotNormal": { - "Foreground": "BrightCyan", - "Background": "Blue" - }, - "HotFocus": { - "Foreground": "BrightBlue", - "Background": "LightGray" - }, - "Disabled": { - "Foreground": "DarkGray", - "Background": "Blue" - } - } - }, - { - "Dialog": { - "Normal": { - "Foreground": "Black", - "Background": "LightGray" - }, - "Focus": { - "Foreground": "DarkGray", - "Background": "LightGray" - }, - "HotNormal": { - "Foreground": "Blue", - "Background": "LightGray" - }, - "HotFocus": { - "Foreground": "BrightBlue", - "Background": "LightGray" - }, - "Disabled": { - "Foreground": "Gray", - "Background": "DarkGray" - } - } - }, - { - "Menu": { - "Normal": { - "Foreground": "White", - "Background": "DarkBlue", - "Style": "Reverse" // Not default Bold - }, - "Focus": { - "Foreground": "White", - "Background": "DarkBlue", - "Style": "Bold,Reverse" - }, - "HotNormal": { - "Foreground": "BrightYellow", - "Background": "DarkBlue", - "Style": "Bold,Underline" - }, - "HotFocus": { - "Foreground": "Blue", - "Background": "White", - "Style": "Bold,Underline" - }, - "Disabled": { - "Foreground": "Gray", - "Background": "DarkGray", - "Style": "Faint" - } - } - }, - { - "Error": { - "Normal": { - "Foreground": "Red", - "Background": "Pink" - }, - "Focus": { - "Foreground": "White", - "Background": "BrightRed" - }, - "HotNormal": { - "Foreground": "Black", - "Background": "Pink" - }, - "HotFocus": { - "Foreground": "Pink", - "Background": "BrightRed" - }, - "Disabled": { - "Foreground": "DarkGray", - "Background": "White" - } - } - } - ] - } - } - ] - } - """; - - // Capture hardCoded hard-coded scheme colors - ImmutableSortedDictionary hardCodedSchemes = SchemeManager.GetHardCodedSchemes ()!; - - Color hardCodedRunnableNormalFg = hardCodedSchemes ["Runnable"].Normal.Foreground; - Assert.Equal (Color.None.ToString (), hardCodedRunnableNormalFg.ToString ()); - - Assert.Equal (hardCodedSchemes ["Menu"].Normal.Style, SchemeManager.GetSchemesForCurrentTheme () ["Menu"]!.Normal.Style); - - // Capture current scheme colors - Dictionary currentSchemes = SchemeManager.GetSchemes ()!; - - Color currentRunnableNormalFg = currentSchemes ["Runnable"].Normal.Foreground; - - Assert.Equal (Color.None.ToString (), currentRunnableNormalFg.ToString ()); - - // Load the test theme - Load (ConfigLocations.Runtime); - Assert.Equal ("TestTheme", ThemeManager.Theme); - Assert.Equal (TextStyle.Reverse, SchemeManager.GetSchemesForCurrentTheme () ["Menu"]!.Normal.Style); - - currentSchemes = SchemeManager.GetSchemesForCurrentTheme ()!; - currentRunnableNormalFg = currentSchemes ["Runnable"].Normal.Foreground; - Assert.NotEqual (hardCodedRunnableNormalFg.ToString (), currentRunnableNormalFg.ToString ()); - - // Now reset everything and reload - ResetToHardCodedDefaults (); - - // Verify we're back to default - Assert.Equal ("Default", ThemeManager.Theme); - - currentSchemes = SchemeManager.GetSchemes ()!; - currentRunnableNormalFg = currentSchemes ["Runnable"].Normal.Foreground; - Assert.Equal (hardCodedRunnableNormalFg.ToString (), currentRunnableNormalFg.ToString ()); - } - finally - { - Disable (true); - } - } - - [Fact] - public void Load_Modified_Default_Scheme_Loads () - { - try - { - Enable (ConfigLocations.HardCoded); - ThrowOnJsonErrors = true; - - // Create a test theme - RuntimeConfig = """ - { - "Theme": "Default", - "Themes": [ - { - "Default": { - "Schemes": [ - { - "Runnable": { - "Normal": { - "Foreground": "AntiqueWhite", - "Background": "DimGray" - }, - "Focus": { - "Foreground": "White", - "Background": "DarkGray" - }, - "HotNormal": { - "Foreground": "Wheat", - "Background": "DarkGray", - "Style": "Underline" - }, - "HotFocus": { - "Foreground": "LightYellow", - "Background": "DimGray", - "Style": "Underline" - }, - "Disabled": { - "Foreground": "Black", - "Background": "DimGray" - } - } - }, - { - "Base": { - "Normal": { - "Foreground": "White", - "Background": "Blue" - }, - "Focus": { - "Foreground": "DarkBlue", - "Background": "LightGray" - }, - "HotNormal": { - "Foreground": "BrightCyan", - "Background": "Blue" - }, - "HotFocus": { - "Foreground": "BrightBlue", - "Background": "LightGray" - }, - "Disabled": { - "Foreground": "DarkGray", - "Background": "Blue" - } - } - }, - { - "Dialog": { - "Normal": { - "Foreground": "Black", - "Background": "LightGray" - }, - "Focus": { - "Foreground": "DarkGray", - "Background": "LightGray" - }, - "HotNormal": { - "Foreground": "Blue", - "Background": "LightGray" - }, - "HotFocus": { - "Foreground": "BrightBlue", - "Background": "LightGray" - }, - "Disabled": { - "Foreground": "Gray", - "Background": "DarkGray" - } - } - }, - { - "Menu": { - "Normal": { - "Foreground": "White", - "Background": "DarkBlue", - "Style": "Reverse" // Not default Bold - }, - "Focus": { - "Foreground": "White", - "Background": "DarkBlue", - "Style": "Bold,Reverse" - }, - "HotNormal": { - "Foreground": "BrightYellow", - "Background": "DarkBlue", - "Style": "Bold,Underline" - }, - "HotFocus": { - "Foreground": "Blue", - "Background": "White", - "Style": "Bold,Underline" - }, - "Disabled": { - "Foreground": "Gray", - "Background": "DarkGray", - "Style": "Faint" - } - } - }, - { - "Error": { - "Normal": { - "Foreground": "Red", - "Background": "Pink" - }, - "Focus": { - "Foreground": "White", - "Background": "BrightRed" - }, - "HotNormal": { - "Foreground": "Black", - "Background": "Pink" - }, - "HotFocus": { - "Foreground": "Pink", - "Background": "BrightRed" - }, - "Disabled": { - "Foreground": "DarkGray", - "Background": "White" - } - } - } - ] - } - } - ] - } - """; - - // Capture hardCoded hard-coded scheme colors - ImmutableSortedDictionary hardCodedSchemes = SchemeManager.GetHardCodedSchemes ()!; - - Color hardCodedRunnableNormalFg = hardCodedSchemes ["Runnable"].Normal.Foreground; - Assert.Equal (Color.None.ToString (), hardCodedRunnableNormalFg.ToString ()); - - Assert.Equal (hardCodedSchemes ["Menu"].Normal.Style, SchemeManager.GetSchemesForCurrentTheme () ["Menu"]!.Normal.Style); - - // Capture current scheme colors - Dictionary currentSchemes = SchemeManager.GetSchemes ()!; - - Color currentRunnableNormalFg = currentSchemes ["Runnable"].Normal.Foreground; - - Assert.Equal (Color.None.ToString (), currentRunnableNormalFg.ToString ()); - - // Load the test theme - Load (ConfigLocations.Runtime); - Assert.Equal ("Default", ThemeManager.Theme); - - // BUGBUG: We did not Apply after loading, so schemes should NOT have been updated - Assert.Equal (TextStyle.Reverse, SchemeManager.GetSchemesForCurrentTheme () ["Menu"]!.Normal.Style); - - currentSchemes = SchemeManager.GetSchemesForCurrentTheme ()!; - currentRunnableNormalFg = currentSchemes ["Runnable"].Normal.Foreground; - - // BUGBUG: We did not Apply after loading, so schemes should NOT have been updated - //Assert.Equal (hardCodedRunnableNormalFg.ToString (), currentRunnableNormalFg.ToString ()); - - // Now reset everything and reload - ResetToHardCodedDefaults (); - - // Verify we're back to default - Assert.Equal ("Default", ThemeManager.Theme); - - currentSchemes = SchemeManager.GetSchemes ()!; - currentRunnableNormalFg = currentSchemes ["Runnable"].Normal.Foreground; - Assert.Equal (hardCodedRunnableNormalFg.ToString (), currentRunnableNormalFg.ToString ()); - } - finally - { - Disable (true); - } - } - - [Fact] - public void Load_From_Json_Does_Not_Corrupt_HardCodedSchemes () - { - try - { - Enable (ConfigLocations.HardCoded); - - // Create a test theme - var json = """ - { - "Theme": "TestTheme", - "Themes": [ - { - "TestTheme": { - "Schemes": [ - { - "Runnable": { - "Normal": { - "Foreground": "AntiqueWhite", - "Background": "DimGray" - }, - "Focus": { - "Foreground": "White", - "Background": "DarkGray" - }, - "HotNormal": { - "Foreground": "Wheat", - "Background": "DarkGray", - "Style": "Underline" - }, - "HotFocus": { - "Foreground": "LightYellow", - "Background": "DimGray", - "Style": "Underline" - }, - "Disabled": { - "Foreground": "Black", - "Background": "DimGray" - } - } - }, - { - "Base": { - "Normal": { - "Foreground": "White", - "Background": "Blue" - }, - "Focus": { - "Foreground": "DarkBlue", - "Background": "LightGray" - }, - "HotNormal": { - "Foreground": "BrightCyan", - "Background": "Blue" - }, - "HotFocus": { - "Foreground": "BrightBlue", - "Background": "LightGray" - }, - "Disabled": { - "Foreground": "DarkGray", - "Background": "Blue" - } - } - }, - { - "Dialog": { - "Normal": { - "Foreground": "Black", - "Background": "LightGray" - }, - "Focus": { - "Foreground": "DarkGray", - "Background": "LightGray" - }, - "HotNormal": { - "Foreground": "Blue", - "Background": "LightGray" - }, - "HotFocus": { - "Foreground": "BrightBlue", - "Background": "LightGray" - }, - "Disabled": { - "Foreground": "Gray", - "Background": "DarkGray" - } - } - }, - { - "Menu": { - "Normal": { - "Foreground": "White", - "Background": "DarkBlue", - "Style": "Reverse" // Not default Bold - }, - "Focus": { - "Foreground": "White", - "Background": "DarkBlue", - "Style": "Bold,Reverse" - }, - "HotNormal": { - "Foreground": "BrightYellow", - "Background": "DarkBlue", - "Style": "Bold,Underline" - }, - "HotFocus": { - "Foreground": "Blue", - "Background": "White", - "Style": "Bold,Underline" - }, - "Disabled": { - "Foreground": "Gray", - "Background": "DarkGray", - "Style": "Faint" - } - } - }, - { - "Error": { - "Normal": { - "Foreground": "Red", - "Background": "Pink" - }, - "Focus": { - "Foreground": "White", - "Background": "BrightRed" - }, - "HotNormal": { - "Foreground": "Black", - "Background": "Pink" - }, - "HotFocus": { - "Foreground": "Pink", - "Background": "BrightRed" - }, - "Disabled": { - "Foreground": "DarkGray", - "Background": "White" - } - } - } - ] - } - } - ] - } - """; - - // Capture dynamically created hardCoded hard-coded scheme colors - ImmutableSortedDictionary hardCodedSchemes = SchemeManager.GetHardCodedSchemes ()!; - - Color hardCodedRunnableNormalFg = hardCodedSchemes ["Runnable"].Normal.Foreground; - Assert.Equal (Color.None.ToString (), hardCodedRunnableNormalFg.ToString ()); - - // Capture current scheme colors - Dictionary currentSchemes = SchemeManager.GetSchemes ()!; - Color currentRunnableNormalFg = currentSchemes ["Runnable"].Normal.Foreground; - Assert.Equal (Color.None.ToString (), currentRunnableNormalFg.ToString ()); - - // Load the test theme - ConfigurationManager.SourcesManager?.Load (Settings, json, "UpdateFromJson", ConfigLocations.Runtime); - - Assert.Equal ("TestTheme", ThemeManager.Theme); - Assert.Equal (TextStyle.Reverse, SchemeManager.GetSchemesForCurrentTheme () ["Menu"]!.Normal.Style); - - Dictionary? hardCodedSchemesViaScope = - GetHardCodedConfigPropertiesByScope ("ThemeScope").ToFrozenDictionary () ["Schemes"].PropertyValue as Dictionary; - Assert.Equal (hardCodedRunnableNormalFg.ToString (), hardCodedSchemesViaScope! ["Runnable"].Normal.Foreground.ToString ()); - } - finally - { - Disable (true); - } - } - - [Fact (Skip = "WIP")] - public void Apply_UpdatesSchemes () - { - Enable (ConfigLocations.HardCoded); - - Assert.False (SchemeManager.GetSchemes ().ContainsKey ("test")); - Assert.Equal (5, SchemeManager.GetSchemes ().Count); // base, runnable, menu, error, dialog - - var theme = new ThemeScope (); - Assert.NotEmpty (theme); - - ThemeManager.Themes!.TryAdd ("testTheme", theme); - - var scheme = new Scheme { Normal = new Attribute (Color.Red, Color.Green) }; - - theme ["Schemes"].PropertyValue = new Dictionary (StringComparer.InvariantCultureIgnoreCase) { { "test", scheme } }; - - Assert.Equal (new Color (Color.Red), ((Dictionary)theme ["Schemes"].PropertyValue!) ["test"].Normal.Foreground); - - Assert.Equal (new Color (Color.Green), ((Dictionary)theme ["Schemes"].PropertyValue!) ["test"].Normal.Background); - - // Act - ThemeManager.Theme = "testTheme"; - ThemeManager.Themes! [ThemeManager.Theme]!.Apply (); - Assert.Equal (5, SchemeManager.GetSchemes ().Count); // base, runnable, menu, error, dialog - - // Assert - Scheme updatedScheme = SchemeManager.GetSchemes () ["test"]!; - Assert.Equal (new Color (Color.Red), updatedScheme.Normal.Foreground); - Assert.Equal (new Color (Color.Green), updatedScheme.Normal.Background); - - // remove test Scheme from Colors to avoid failures on others unit tests with Scheme - SchemeManager.GetSchemes ().Remove ("test"); - Assert.Equal (5, SchemeManager.GetSchemes ().Count); - - Disable (true); - } - - [Fact] - public void AddScheme_Adds_And_Updates_Scheme () - { - // Arrange - var scheme = new Scheme (new Attribute (Color.Red, Color.Green)); - var schemeName = "CustomScheme"; - - // Act - SchemeManager.AddScheme (schemeName, scheme); - - // Assert - Assert.Equal (scheme, SchemeManager.GetScheme (schemeName)); - - // Update the scheme - var updatedScheme = new Scheme (new Attribute (Color.Blue, Color.Yellow)); - SchemeManager.AddScheme (schemeName, updatedScheme); - - Assert.Equal (updatedScheme, SchemeManager.GetScheme (schemeName)); - - // Cleanup - SchemeManager.RemoveScheme (schemeName); - } - - [Fact] - public void RemoveScheme_Removes_Custom_Scheme () - { - var scheme = new Scheme (new Attribute (Color.Red, Color.Green)); - var schemeName = "RemovableScheme"; - SchemeManager.AddScheme (schemeName, scheme); - - Assert.Equal (scheme, SchemeManager.GetScheme (schemeName)); - - SchemeManager.RemoveScheme (schemeName); - - Assert.Throws (() => SchemeManager.GetScheme (schemeName)); - } -} diff --git a/Tests/UnitTests/Configuration/SettingsScopeTests.cs b/Tests/UnitTests/Configuration/SettingsScopeTests.cs deleted file mode 100644 index ae44a5ff0f..0000000000 --- a/Tests/UnitTests/Configuration/SettingsScopeTests.cs +++ /dev/null @@ -1,382 +0,0 @@ -#nullable enable -using System.Collections.Concurrent; -using System.Collections.Frozen; -using System.Collections.Immutable; -using System.Text.Json; -using static Terminal.Gui.Configuration.ConfigurationManager; - -namespace UnitTests.ConfigurationTests; - -public class SettingsScopeTests -{ - [Fact] - public void Load_Overrides_Defaults () - { - // arrange - Enable (ConfigLocations.HardCoded); - - Dictionary defaultBindings = - (Dictionary)Settings! ["Application.DefaultKeyBindings"].PropertyValue!; - Assert.NotNull (defaultBindings); - Assert.Contains (Command.Quit, defaultBindings.Keys); - - ThrowOnJsonErrors = true; - - // act - RuntimeConfig = """ - { - "Application.DefaultKeyBindings": { - "Quit": { "All": ["Ctrl+Q"] } - } - } - """; - - Load (ConfigLocations.Runtime); - - // assert - Dictionary updatedBindings = - (Dictionary)Settings ["Application.DefaultKeyBindings"].PropertyValue!; - Assert.NotNull (updatedBindings); - Key [] quitKeys = updatedBindings [Command.Quit].All!; - Assert.Single (quitKeys); - Assert.Equal (Key.Q.WithCtrl, quitKeys [0]); - - // clean up - Disable (true); - } - - [Fact] - public void Load_Dictionary_Property_Overrides_Defaults () - { - // arrange - Enable (ConfigLocations.HardCoded); - ThrowOnJsonErrors = true; - - ConfigProperty themesConfigProperty = Settings! ["Themes"]; - ConcurrentDictionary dict = (themesConfigProperty.PropertyValue as ConcurrentDictionary)!; - - Assert.NotNull (dict); - Assert.Single (dict); - Assert.NotEmpty (((ConcurrentDictionary)themesConfigProperty.PropertyValue!)!); - - ThemeScope scope = dict [ThemeManager.DEFAULT_THEME_NAME]; - Assert.NotNull (scope); - Assert.Equal (MouseState.In | MouseState.Pressed | MouseState.PressedOutside, scope ["Button.DefaultMouseHighlightStates"].PropertyValue); - - RuntimeConfig = """ - { - "Themes": [ - { - "Default": - { - "Button.DefaultMouseHighlightStates": "None" - } - }, - { - "NewTheme": - { - "Button.DefaultMouseHighlightStates": "In" - } - } - ] - } - """; - - Load (ConfigLocations.Runtime); - - // assert - Assert.Equal (2, ThemeManager.Themes!.Count); - Assert.Equal (MouseState.None, (MouseState)ThemeManager.GetCurrentTheme () ["Button.DefaultMouseHighlightStates"].PropertyValue!); - Assert.Equal (MouseState.In, (MouseState)ThemeManager.Themes ["NewTheme"] ["Button.DefaultMouseHighlightStates"].PropertyValue!); - - RuntimeConfig = """ - { - "Themes": [ - { - "Default": - { - "Button.DefaultMouseHighlightStates": "Pressed" - } - } - ] - } - """; - Load (ConfigLocations.Runtime); - - // assert - Assert.Equal (2, ThemeManager.Themes.Count); - Assert.Equal (MouseState.Pressed, (MouseState)ThemeManager.Themes! [ThemeManager.DEFAULT_THEME_NAME] ["Button.DefaultMouseHighlightStates"].PropertyValue!); - Assert.Equal (MouseState.In, (MouseState)ThemeManager.Themes! ["NewTheme"] ["Button.DefaultMouseHighlightStates"].PropertyValue!); - - // clean up - Disable (true); - } - - [Fact] - public void Apply_ShouldApplyProperties () - { - Enable (ConfigLocations.HardCoded); - Load (ConfigLocations.LibraryResources); - - // arrange — verify default bindings are present - Dictionary bindings = - (Dictionary)Settings! ["Application.DefaultKeyBindings"].PropertyValue!; - Assert.Contains (Command.Quit, bindings.Keys); - - // act — replace with new bindings - Dictionary newBindings = new () - { - [Command.Quit] = Bind.All (Key.Q), - [Command.NextTabGroup] = Bind.All (Key.F), - [Command.PreviousTabGroup] = Bind.All (Key.B), - }; - Settings ["Application.DefaultKeyBindings"].PropertyValue = newBindings; - - Settings.Apply (); - - // assert - Assert.Equal (Key.Q, Application.GetDefaultKey (Command.Quit)); - Assert.Equal (Key.F, Application.GetDefaultKey (Command.NextTabGroup)); - Assert.Equal (Key.B, Application.GetDefaultKey (Command.PreviousTabGroup)); - - Disable (true); - } - - [Fact] - public void CopyUpdatedPropertiesFrom_ShouldCopyChangedPropertiesOnly () - { - Enable (ConfigLocations.HardCoded); - - // Set DefaultKeyBindings to a custom value - Dictionary customBindings = new () - { - [Command.Quit] = Bind.All (Key.End), - }; - Settings! ["Application.DefaultKeyBindings"].PropertyValue = customBindings; - - SettingsScope updatedSettings = new (); - updatedSettings.LoadHardCodedDefaults (); - - // Mark DefaultKeyBindings as not having a value (should not overwrite) - updatedSettings ["Application.DefaultKeyBindings"].HasValue = false; - updatedSettings ["Application.DefaultKeyBindings"].PropertyValue = null; - - // Set a different property to verify it IS copied - updatedSettings ["FileDialog.MaxSearchResults"].PropertyValue = 42; - - Settings.UpdateFrom (updatedSettings); - - // DefaultKeyBindings should still have our custom value - Dictionary result = - (Dictionary)Settings ["Application.DefaultKeyBindings"].PropertyValue!; - Assert.Contains (Command.Quit, result.Keys); - - // MaxSearchResults should be updated - Assert.Equal (42, Settings ["FileDialog.MaxSearchResults"].PropertyValue); - - Disable (true); - } - - [Fact] - public void ResetToHardCodedDefaults_Resets_Config_And_Applies () - { - try - { - Enable (ConfigLocations.HardCoded); - Load (ConfigLocations.LibraryResources); - - Assert.True (Settings! ["Application.DefaultKeyBindings"].PropertyValue is Dictionary); - Assert.Equal (Key.Esc, Application.GetDefaultKey (Command.Quit)); - - // Change to a custom value - Dictionary customBindings = new () - { - [Command.Quit] = Bind.All (Key.Q), - }; - Settings ["Application.DefaultKeyBindings"].PropertyValue = customBindings; - Apply (); - Assert.Equal (Key.Q, Application.GetDefaultKey (Command.Quit)); - - // Act - ResetToHardCodedDefaults (); - Dictionary resetBindings = - (Dictionary)Settings ["Application.DefaultKeyBindings"].PropertyValue!; - Assert.Contains (Command.Quit, resetBindings.Keys); - Assert.Equal (Key.Esc, Application.GetDefaultKey (Command.Quit)); - } - finally - { - Disable (true); - } - } - - [Fact] - public void Themes_Property_Exists () - { - var settingsScope = new SettingsScope (); - - Assert.NotEmpty (settingsScope); - - // Themes exists, but is not initialized - Assert.Null (settingsScope ["Themes"].PropertyValue); - - //settingsScope.UpdateToCurrentValues (); - - //Assert.NotEmpty (settingsScope); - } - - [Fact] - public void LoadHardCodedDefaults_Resets () - { - // Arrange - Assert.Equal (Key.Esc, Application.GetDefaultKey (Command.Quit)); - SettingsScope settingsScope = new (); - settingsScope.LoadHardCodedDefaults (); - - // Act — change the quit key - Dictionary customBindings = new () - { - [Command.Quit] = Bind.All (Key.Q), - }; - settingsScope ["Application.DefaultKeyBindings"].PropertyValue = customBindings; - settingsScope.Apply (); - Assert.Equal (Key.Q, Application.GetDefaultKey (Command.Quit)); - - settingsScope.LoadHardCodedDefaults (); - settingsScope.Apply (); - - // Assert — should be back to default - Assert.Equal (Key.Esc, Application.GetDefaultKey (Command.Quit)); - - Disable (true); - } - - private class ConfigPropertyMock - { - public object? PropertyValue { get; init; } - public bool Immutable { get; init; } - } - - private class SettingsScopeMock : Dictionary - { - public string? Theme { get; set; } - } - - [Fact] - public void SettingsScopeMockWithKey_CreatesDeepCopy () - { - SettingsScopeMock? source = new () - { - Theme = "Dark", - ["KeyBinding"] = new () { PropertyValue = new Key (KeyCode.A) { Handled = true } }, - ["Counts"] = new () { PropertyValue = new Dictionary { { "X", 1 } } } - }; - SettingsScopeMock? result = DeepCloner.DeepClone (source); - - Assert.NotNull (result); - Assert.NotSame (source, result); - Assert.Equal (source.Theme, result!.Theme); - Assert.NotSame (source ["KeyBinding"], result ["KeyBinding"]); - Assert.NotSame (source ["Counts"], result ["Counts"]); - - ConfigPropertyMock clonedKeyProp = result ["KeyBinding"]; - var clonedKey = (Key)clonedKeyProp.PropertyValue!; - Assert.NotSame (source ["KeyBinding"].PropertyValue, clonedKey); - Assert.Equal (((Key)source ["KeyBinding"].PropertyValue!).KeyCode, clonedKey.KeyCode); - Assert.Equal (((Key)source ["KeyBinding"].PropertyValue!).Handled, clonedKey.Handled); - - Assert.Equal ((Dictionary)source ["Counts"].PropertyValue!, (Dictionary)result ["Counts"].PropertyValue!); - - // Modify result, ensure source unchanged - result.Theme = "Light"; - clonedKey.Handled = false; - ((Dictionary)result ["Counts"].PropertyValue!).Add ("Y", 2); - Assert.Equal ("Dark", source.Theme); - Assert.True (((Key)source ["KeyBinding"].PropertyValue!).Handled); - Assert.Single ((Dictionary)source ["Counts"].PropertyValue!); - Disable (true); - } - - [Fact /*(Skip = "This test randomly fails due to a concurrent change to something. Needs to be moved to non-parallel tests.")*/] - public void ThemeScopeList_WithThemes_ClonesSuccessfully () - { - // Arrange: Create a ThemeScope and verify a property exists - var defaultThemeScope = new ThemeScope (); - defaultThemeScope.LoadHardCodedDefaults (); - Assert.True (defaultThemeScope.ContainsKey ("Button.DefaultMouseHighlightStates")); - - var darkThemeScope = new ThemeScope (); - darkThemeScope.LoadHardCodedDefaults (); - Assert.True (darkThemeScope.ContainsKey ("Button.DefaultMouseHighlightStates")); - - // Create a Themes list with two themes - List> themesList = - [ - new () { { "Default", defaultThemeScope } }, - new () { { "Dark", darkThemeScope } } - ]; - - // Create a SettingsScope and set the Themes property - var settingsScope = new SettingsScope (); - settingsScope.LoadHardCodedDefaults (); - Assert.True (settingsScope.ContainsKey ("Themes")); - settingsScope ["Themes"].PropertyValue = themesList; - - // Act - SettingsScope? result = DeepCloner.DeepClone (settingsScope); - - // Assert - Assert.NotNull (result); - Assert.IsType (result); - var resultScope = result; - Assert.True (resultScope.ContainsKey ("Themes")); - - Assert.NotNull (resultScope ["Themes"].PropertyValue); - - List> clonedThemes = (List>)resultScope ["Themes"].PropertyValue!; - Assert.Equal (2, clonedThemes.Count); - Disable (true); - } - - [Fact] - public void Empty_SettingsScope_ClonesSuccessfully () - { - // Arrange: Create a SettingsScope - var settingsScope = new SettingsScope (); - Assert.True (settingsScope.ContainsKey ("Themes")); - - // Act - SettingsScope? result = DeepCloner.DeepClone (settingsScope); - - // Assert - Assert.NotNull (result); - Assert.IsType (result); - - Assert.True (result.ContainsKey ("Themes")); - Disable (true); - } - - [Fact] - public void SettingsScope_With_Themes_Set_ClonesSuccessfully () - { - // Arrange: Create a SettingsScope - var settingsScope = new SettingsScope (); - Assert.True (settingsScope.ContainsKey ("Themes")); - - settingsScope ["Themes"].PropertyValue = new List> - { - new () { { "Default", new () } }, - new () { { "Dark", new () } } - }; - - // Act - SettingsScope? result = DeepCloner.DeepClone (settingsScope); - - // Assert - Assert.NotNull (result); - Assert.IsType (result); - Assert.True (result.ContainsKey ("Themes")); - Assert.NotNull (result ["Themes"].PropertyValue); - Disable (true); - } -} diff --git a/Tests/UnitTests/Configuration/SourcesManagerTests.cs b/Tests/UnitTests/Configuration/SourcesManagerTests.cs deleted file mode 100644 index 8ff31f0050..0000000000 --- a/Tests/UnitTests/Configuration/SourcesManagerTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Reflection; -using System.Text.Json; - -namespace UnitTests.ConfigurationTests; - -public class SourcesManagerTests -{ - [Fact] - public void Sources_StaysConsistentWhenUpdateFails () - { - // Arrange - var sourcesManager = new SourcesManager (); - var settingsScope = new SettingsScope (); - - // Add one successful source - var validSource = "valid.json"; - var validLocation = ConfigLocations.Runtime; - sourcesManager.Load (settingsScope, """{"Driver.Force16Colors": true}""", validSource, validLocation); - - try - { - // Configure to throw on errors - ConfigurationManager.ThrowOnJsonErrors = true; - - // Act & Assert - attempt to update with invalid JSON - var invalidSource = "invalid.json"; - var invalidLocation = ConfigLocations.AppCurrent; - var invalidJson = "{ invalid json }"; - - Assert.Throws ( - () => - sourcesManager.Load (settingsScope, invalidJson, invalidSource, invalidLocation)); - - // The valid source should still be there - Assert.Single (sourcesManager.Sources); - Assert.Equal (validSource, sourcesManager.Sources [validLocation]); - - // The invalid source should not have been added - Assert.DoesNotContain (invalidLocation, sourcesManager.Sources.Keys); - } - finally - { - // Reset for other tests - ConfigurationManager.ThrowOnJsonErrors = false; - } - } - - - // NOTE: This test causes the static CM._jsonErrors to be modified; can't use in a parallel test - [Fact] - public void Load_WithInvalidJson_AddsJsonError () - { - // Arrange - var sourcesManager = new SourcesManager (); - - var settingsScope = new SettingsScope (); - var invalidJson = "{ invalid json }"; - var stream = new MemoryStream (); - var writer = new StreamWriter (stream); - writer.Write (invalidJson); - writer.Flush (); - stream.Position = 0; - - var source = "Load_WithInvalidJson_AddsJsonError"; - var location = ConfigLocations.AppCurrent; - - // Act - bool result = sourcesManager.Load (settingsScope, stream, source, location); - - // Assert - Assert.False (result); - - // Assuming AddJsonError logs errors, verify the error was logged (mock or inspect logs if possible). - } -} diff --git a/Tests/UnitTests/Configuration/ThemeManagerTests.cs b/Tests/UnitTests/Configuration/ThemeManagerTests.cs deleted file mode 100644 index 54f8999eff..0000000000 --- a/Tests/UnitTests/Configuration/ThemeManagerTests.cs +++ /dev/null @@ -1,287 +0,0 @@ -#nullable enable -using System.Collections.Concurrent; -using static Terminal.Gui.Configuration.ConfigurationManager; - -namespace UnitTests.ConfigurationTests; - -public class ThemeManagerTests (ITestOutputHelper output) -{ - [Fact] - public void ResetToCurrentValues_Adds_Default_Theme () - { - try - { - Enable (ConfigLocations.HardCoded); - Assert.NotEmpty (ThemeManager.Themes!); - - ThemeManager.UpdateToCurrentValues (); - - Assert.NotEmpty (ThemeManager.Themes!); - - // Default theme exists - Assert.NotNull (ThemeManager.Themes? [ThemeManager.DEFAULT_THEME_NAME]); - } - finally - { - Disable (resetToHardCodedDefaults: true); - } - } - - // ResetToCurrentValues - - // OnThemeChanged - - #region Tests Settings["Theme"] and ThemeManager.Theme - - [Fact] - public void Theme_Settings_Theme_Equals_ThemeManager_Theme () - { - Assert.False (IsEnabled); - - Assert.Equal (Settings! ["Theme"].PropertyValue, ThemeManager.Theme); - Assert.Equal (ThemeManager.DEFAULT_THEME_NAME, ThemeManager.Theme); - } - - [Fact] - public void Theme_Enabled_Settings_Theme_Equals_ThemeManager_Theme () - { - Assert.False (IsEnabled); - - Enable (ConfigLocations.HardCoded); - - Assert.Equal (Settings! ["Theme"].PropertyValue, ThemeManager.Theme); - Assert.Equal (ThemeManager.DEFAULT_THEME_NAME, ThemeManager.Theme); - - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void Theme_Set_Sets () - { - Assert.False (IsEnabled); - - Enable (ConfigLocations.HardCoded); - - Assert.Equal (ThemeManager.DEFAULT_THEME_NAME, ThemeManager.Theme); - - ThemeManager.Theme = "Test"; - Assert.Equal ("Test", ThemeManager.Theme); - Assert.Equal (Settings! ["Theme"].PropertyValue, ThemeManager.Theme); - Assert.Equal ("Test", Settings! ["Theme"].PropertyValue); - - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void Theme_ResetToHardCodedDefaults_Sets_To_Default () - { - try - { - Assert.False (IsEnabled); - Assert.Equal (ThemeManager.DEFAULT_THEME_NAME, ThemeManager.Theme); - - Enable (ConfigLocations.HardCoded); - Assert.Equal ("Default", ThemeManager.Theme); - - ThemeManager.Theme = "Test"; - Assert.Equal ("Test", ThemeManager.Theme); - Assert.Equal (Settings! ["Theme"].PropertyValue, ThemeManager.Theme); - Assert.Equal ("Test", Settings! ["Theme"].PropertyValue); - - ResetToHardCodedDefaults (); - Assert.Equal ("Default", ThemeManager.Theme); - } - finally - { - Disable(true); - } - } - - #endregion Tests Settings["Theme"] and ThemeManager.Theme - - #region Tests Settings["Themes"] and ThemeManager.Themes - - [Fact] - public void Themes_Set_Throws_If_Not_Enabled () - { - Assert.False (IsEnabled); - - Assert.Single (ThemeManager.Themes!); - Assert.Throws (() => ThemeManager.Themes = new ()); - Assert.Single (ThemeManager.Themes!); - } - - [Fact] - public void Themes_Set_Sets_If_Enabled () - { - Assert.False (IsEnabled); - - Enable (ConfigLocations.HardCoded); - - Assert.Single (ThemeManager.Themes!); - - // Use ConcurrentDictionary instead of a regular dictionary - ThemeManager.Themes = new ConcurrentDictionary ( - new Dictionary - { - { "Default", new ThemeScope() }, - { "test", new ThemeScope() } - }, - StringComparer.InvariantCultureIgnoreCase - ); - - Assert.Contains ("test", ThemeManager.Themes!); - - Disable (resetToHardCodedDefaults: true); - } - - - [Fact] - public void Themes_Set_Throws_If_No_Default_Theme_In_Dictionary () - { - Assert.False (IsEnabled); - - Enable (ConfigLocations.HardCoded); - - Assert.Single (ThemeManager.Themes!); - - Assert.Throws ( - () => ThemeManager.Themes = new ConcurrentDictionary ( - new Dictionary - { - { "test", new ThemeScope() } - }, - StringComparer.InvariantCultureIgnoreCase - )); - Assert.Single (ThemeManager.Themes!); - - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void Themes_Get () { } - - #endregion Tests Settings["Themes"] and ThemeManager.Themes - - [Fact] - public void Themes_TryAdd_Adds () - { - Enable (ConfigLocations.HardCoded); - - // Verify that the Themes dictionary contains only the Default theme - Assert.Single (ThemeManager.Themes!); - Assert.Contains (ThemeManager.DEFAULT_THEME_NAME, ThemeManager.Themes!); - - var theme = new ThemeScope (); - theme.LoadHardCodedDefaults (); - Assert.NotEmpty (theme); - - Assert.True (ThemeManager.Themes!.TryAdd ("testTheme", theme)); - Assert.Equal (2, ThemeManager.Themes.Count); - - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void Apply_Applies () - { - Assert.False (IsEnabled); - Enable (ConfigLocations.HardCoded); - - var theme = new ThemeScope (); - theme.LoadHardCodedDefaults (); - Assert.NotEmpty (theme); - - Assert.True (ThemeManager.Themes!.TryAdd ("testTheme", theme)); - Assert.Equal (2, ThemeManager.Themes.Count); - - Assert.Equal (LineStyle.Rounded, FrameView.DefaultBorderStyle); - theme ["FrameView.DefaultBorderStyle"].PropertyValue = LineStyle.Double; // default is Single - - ThemeManager.Theme = "testTheme"; - ThemeManager.Themes! [ThemeManager.Theme]!.Apply (); - - Assert.Equal (LineStyle.Double, FrameView.DefaultBorderStyle); - - Disable (resetToHardCodedDefaults: true); - } - - [Fact] - public void Theme_Reload_Consistency () - { - try - { - Enable (ConfigLocations.HardCoded); - - // BUGBUG: Setting Schemes to empty array is not valid! - // Create a test theme - RuntimeConfig = """ - { - "Theme": "TestTheme", - "Themes": [ - { - "TestTheme": { - "Schemes": [] - } - } - ] - } - """; - - // Load the test theme - ThrowOnJsonErrors = true; - Load (ConfigLocations.Runtime); - Assert.Equal ("TestTheme", ThemeManager.Theme); - - // Now reset everything and reload - ResetToHardCodedDefaults (); - - // Verify we're back to default - Assert.Equal ("Default", ThemeManager.Theme); - } - finally - { - Disable (resetToHardCodedDefaults: true); - } - } - - [Fact] - public void In_Memory_Themes_Size_Is_Reasonable () - { - output.WriteLine ($"Start: Color size: {(MemorySizeEstimator.EstimateSize (Color.Red))} b"); - - output.WriteLine ($"Start: Attribute size: {(MemorySizeEstimator.EstimateSize (Attribute.Default))} b"); - - output.WriteLine ($"Start: Base Scheme size: {(MemorySizeEstimator.EstimateSize (Scheme.GetHardCodedSchemes ()))} b"); - - output.WriteLine ($"Start: PropertyInfo size: {(MemorySizeEstimator.EstimateSize (ConfigurationManager.Settings! ["Application.DefaultKeyBindings"]))} b"); - - ThemeScope themeScope = new ThemeScope (); - output.WriteLine ($"Start: ThemeScope ({themeScope.Count}) size: {(MemorySizeEstimator.EstimateSize (themeScope))} b"); - - themeScope.AddValue ("Schemes", Scheme.GetHardCodedSchemes ()); - output.WriteLine ($"Start: ThemeScope ({themeScope.Count}) size: {(MemorySizeEstimator.EstimateSize (themeScope))} b"); - - output.WriteLine ($"Start: HardCoded Schemes ({SchemeManager.Schemes!.Count}) size: {(MemorySizeEstimator.EstimateSize (SchemeManager.Schemes!))} b"); - - output.WriteLine ($"Start: Themes dictionary ({ThemeManager.Themes!.Count}) size: {(MemorySizeEstimator.EstimateSize (ThemeManager.Themes!)) / 1024} Kb"); - - Enable (ConfigLocations.HardCoded); - - output.WriteLine ($"Enabled: Themes dictionary ({ThemeManager.Themes.Count}) size: {(MemorySizeEstimator.EstimateSize (ThemeManager.Themes!)) / 1024} Kb"); - - Load (ConfigLocations.LibraryResources); - output.WriteLine ($"After Load: Themes dictionary ({ThemeManager.Themes!.Count}) size: {(MemorySizeEstimator.EstimateSize (ThemeManager.Themes!)) / 1024} Kb"); - - output.WriteLine ($"Total Settings Size: {(MemorySizeEstimator.EstimateSize (Settings!)) / 1024} Kb"); - - string json = ConfigurationManager.SourcesManager?.ToJson (Settings)!; - - // In memory size should be less than the size of the json - output.WriteLine ($"JSON size: {json.Length / 1024} Kb"); - - Assert.True (70000 > MemorySizeEstimator.EstimateSize (ThemeManager.Themes!), $"In memory size ({(MemorySizeEstimator.EstimateSize (Settings!)) / 1024} Kb) is > json size ({json.Length / 1024} Kb)"); - - Disable (resetToHardCodedDefaults: true); - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Configuration/ThemeScopeTests.cs b/Tests/UnitTests/Configuration/ThemeScopeTests.cs deleted file mode 100644 index 2eaf681ce0..0000000000 --- a/Tests/UnitTests/Configuration/ThemeScopeTests.cs +++ /dev/null @@ -1,231 +0,0 @@ -#nullable enable -using System.Collections.Concurrent; -using System.Collections.Frozen; -using System.Collections.Immutable; -using System.Text.Json; -using static Terminal.Gui.Configuration.ConfigurationManager; - -namespace UnitTests.ConfigurationTests; - -public class ThemeScopeTests -{ - [Fact] - public void Load_AllThemesPresent () - { - Enable (ConfigLocations.HardCoded); - - Load (ConfigLocations.All); - Assert.True (ThemeManager.Themes!.ContainsKey ("Default")); - Assert.True (ThemeManager.Themes.ContainsKey ("Dark")); - Assert.True (ThemeManager.Themes.ContainsKey ("Light")); - Disable (true); - } - - [Fact] - public void Apply_ShouldApplyUpdatedProperties () - { - Enable (ConfigLocations.HardCoded); - Assert.NotEmpty (ThemeManager.Themes!); - - Alignment savedButtonAlignment = Dialog.DefaultButtonAlignment; - Alignment newButtonAlignment = Alignment.Center != savedButtonAlignment ? Alignment.Center : Alignment.Start; - ThemeManager.GetCurrentTheme () ["Dialog.DefaultButtonAlignment"].PropertyValue = newButtonAlignment; - - LineStyle savedBorderStyle = Dialog.DefaultBorderStyle; - var newBorderStyle = LineStyle.HeavyDotted; - ThemeManager.GetCurrentTheme () ["Dialog.DefaultBorderStyle"].PropertyValue = newBorderStyle; - - ThemeManager.Themes! [ThemeManager.Theme]!.Apply (); - Assert.Equal (newButtonAlignment, Dialog.DefaultButtonAlignment); - Assert.Equal (newBorderStyle, Dialog.DefaultBorderStyle); - - // Replace with the savedValue to avoid failures on other unit tests that rely on the default value - ThemeManager.GetCurrentTheme () ["Dialog.DefaultButtonAlignment"].PropertyValue = savedButtonAlignment; - ThemeManager.GetCurrentTheme () ["Dialog.DefaultBorderStyle"].PropertyValue = savedBorderStyle; - ThemeManager.GetCurrentTheme ().Apply (); - Assert.Equal (savedButtonAlignment, Dialog.DefaultButtonAlignment); - Assert.Equal (savedBorderStyle, Dialog.DefaultBorderStyle); - Disable (true); - } - - [Fact] - public void UpdateToHardCodedDefaults_Resets_Config_Does_Not_Apply () - { - Enable (ConfigLocations.HardCoded); - - Load (ConfigLocations.LibraryResources); - - Assert.Equal ("Default", ThemeManager.Theme); - ThemeManager.Theme = "Dark"; - Assert.Equal ("Dark", ThemeManager.Theme); - Apply (); - Assert.Equal ("Dark", ThemeManager.Theme); - - // Act - ThemeManager.LoadHardCodedDefaults (); - Assert.Equal ("Default", ThemeManager.Theme); - - Disable (true); - } - - [Fact] - public void Serialize_Themes_RoundTrip () - { - Enable (ConfigLocations.HardCoded); - - IDictionary initial = ThemeManager.Themes!; - - string serialized = JsonSerializer.Serialize (ThemeManager.Themes, SerializerContext.Options); - - ConcurrentDictionary? deserialized = - JsonSerializer.Deserialize> (serialized, SerializerContext.Options); - - Assert.NotEqual (initial, deserialized); - Assert.Equal (deserialized!.Count, initial!.Count); - - Disable (true); - } - - - [Fact] - public void DeSerialize_Themes_UpdateFrom_Updates () - { - Enable (ConfigLocations.HardCoded); - - IDictionary initial = ThemeManager.Themes!; - - string serialized = """ - { - "Default": { - "Button.DefaultShadow": "None" - } - } - """; - ConcurrentDictionary? deserialized = - JsonSerializer.Deserialize> (serialized, SerializerContext.Options); - - ShadowStyles initialShadowStyle = (ShadowStyles)(initial! ["Default"] ["Button.DefaultShadow"].PropertyValue!); - Assert.Equal (ShadowStyles.Opaque, initialShadowStyle); - - ShadowStyles deserializedShadowStyle = (ShadowStyles)(deserialized! ["Default"] ["Button.DefaultShadow"].PropertyValue!); - Assert.Equal (ShadowStyles.None, deserializedShadowStyle); - - initial ["Default"].UpdateFrom (deserialized ["Default"]); - initialShadowStyle = (ShadowStyles)(initial! ["Default"] ["Button.DefaultShadow"].PropertyValue!); - Assert.Equal (ShadowStyles.None, initialShadowStyle); - - Assert.Equal(ShadowStyles.Opaque, Button.DefaultShadow); - initial ["Default"].Apply (); - Assert.Equal (ShadowStyles.None, Button.DefaultShadow); - - Disable (true); - Assert.Equal (ShadowStyles.Opaque, Button.DefaultShadow); - - } - - [Fact] - public void Serialize_New_RoundTrip () - { - Enable (ConfigLocations.HardCoded); - - var theme = new ThemeScope (); - theme.LoadHardCodedDefaults (); - theme ["Dialog.DefaultButtonAlignment"].PropertyValue = Alignment.End; - - string json = JsonSerializer.Serialize (theme, SerializerContext.Options); - - var deserialized = JsonSerializer.Deserialize (json, SerializerContext.Options); - - Assert.Equal ( - Alignment.End, - (Alignment)deserialized! ["Dialog.DefaultButtonAlignment"].PropertyValue! - ); - - Disable (true); - } - - [Fact (Skip = "Temp work arounds for #4288 prevent corruption.")] - public void UpdateFrom_Corrupts_Schemes_HardCodeDefaults () - { - // BUGBUG: ThemeScope is broken and needs to be fixed to not have the hard coded schemes get overwritten. - // BUGBUG: This test demonstrates the problem. - // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/4288 - - // Create a test theme - var json = """ - { - "Schemes": [ - { - "Base": { - "Normal": { - "Foreground": "White", - "Background": "Blue" - } - } - } - ] - } - """; - - //var json = """ - // { - // "Themes": [ - // { - // "Default": { - // "Schemes": [ - // { - // "Base": { - // "Normal": { - // "Foreground": "White", - // "Background": "Blue" - // } - // } - // } - // ] - // } - // } - // ] - // } - // """; - - try - { - Assert.False (IsEnabled); - ThrowOnJsonErrors = true; - // Enable (ConfigLocations.HardCoded); - //ResetToCurrentValues (); - - // Capture dynamically created hardCoded hard-coded scheme colors - ImmutableSortedDictionary hardCodedSchemes = SchemeManager.GetHardCodedSchemes ()!; - Color hardCodedBaseNormalFg = hardCodedSchemes ["Base"].Normal.Foreground; - Assert.Equal (new Color (StandardColor.LightBlue).ToString (), hardCodedBaseNormalFg.ToString ()); - - // Capture hard-coded scheme colors via cache - Dictionary? hardCodedSchemesViaCache = - GetHardCodedConfigPropertiesByScope ("ThemeScope")!.ToFrozenDictionary () ["Schemes"].PropertyValue as Dictionary; - Assert.Equal (hardCodedBaseNormalFg.ToString (), hardCodedSchemesViaCache! ["Base"].Normal.Foreground.ToString ()); - - // (ConfigLocations.HardCoded); - - // Capture current scheme - Dictionary currentSchemes = SchemeManager.GetSchemes ()!; - Color currentBaseNormalFg = currentSchemes ["Base"].Normal.Foreground; - Assert.Equal (hardCodedBaseNormalFg.ToString (), currentBaseNormalFg.ToString ()); - - //ConfigurationManager.SourcesManager?.Load (Settings, json, "UpdateFromJson", ConfigLocations.Runtime); - - ThemeScope scope = (JsonSerializer.Deserialize (json, typeof (ThemeScope), SerializerContext.Options) as ThemeScope)!; - - ThemeScope defaultTheme = ThemeManager.Themes! ["Default"]!; - Dictionary schemesScope = (defaultTheme ["Schemes"].PropertyValue as Dictionary)!; - defaultTheme ["Schemes"].UpdateFrom (scope ["Schemes"].PropertyValue!); - defaultTheme.UpdateFrom (scope); - - Assert.Equal (Color.White.ToString (), hardCodedSchemesViaCache! ["Base"].Normal.Foreground.ToString ()); - } - finally - { - ResetToHardCodedDefaults (); - } - } -} diff --git a/Tests/UnitTests/Configuration/ThemeTests.cs b/Tests/UnitTests/Configuration/ThemeTests.cs deleted file mode 100644 index fda4a87135..0000000000 --- a/Tests/UnitTests/Configuration/ThemeTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json; -using static Terminal.Gui.Configuration.ConfigurationManager; - -namespace UnitTests.ConfigurationTests; - -/// -/// Tests Settings["Theme"] and ThemeManager.Theme -/// -public class ThemeTests -{ - public static readonly JsonSerializerOptions _jsonOptions = new () - { - Converters = { new AttributeJsonConverter (), new ColorJsonConverter () } - }; - - -} diff --git a/Tests/UnitTests/Dialogs/WizardTests.cs b/Tests/UnitTests/Dialogs/WizardTests.cs deleted file mode 100644 index 3c6f139d69..0000000000 --- a/Tests/UnitTests/Dialogs/WizardTests.cs +++ /dev/null @@ -1,617 +0,0 @@ -namespace UnitTests.DialogTests; - -public class WizardTests -{ - // =========== Wizard Tests - [Fact] - public void DefaultConstructor_SizedProperly () - { - var wizard = new Wizard (); - Assert.NotEqual (0, wizard.Width); - Assert.NotEqual (0, wizard.Height); - wizard.Dispose (); - } - - [Fact (Skip = "Convoluted test that needs to be rewritten")] - [AutoInitShutdown] - public void Finish_Button_Closes () - { - // https://github.com/gui-cs/Terminal.Gui/issues/1833 - Wizard wizard = new (); - WizardStep step1 = new () { Title = "step1" }; - wizard.AddStep (step1); - - var finishedFired = false; - wizard.Accepting += (s, args) => { finishedFired = true; }; - - var isRunningChangedFired = false; - wizard.IsRunningChanged += (s, e) => { isRunningChangedFired = true; }; - - SessionToken sessionToken = Application.Begin (wizard); - AutoInitShutdownAttribute.RunIteration (); - - wizard.NextFinishButton.InvokeCommand (Command.Accept); - AutoInitShutdownAttribute.RunIteration (); - Application.End (sessionToken); - Assert.True (finishedFired); - Assert.True (isRunningChangedFired); - step1.Dispose (); - wizard.Dispose (); - - // Same test, but with two steps - wizard = new (); - step1 = new () { Title = "step1" }; - wizard.AddStep (step1); - WizardStep step2 = new () { Title = "step2" }; - wizard.AddStep (step2); - - finishedFired = false; - wizard.Accepting += (s, args) => { finishedFired = true; }; - - isRunningChangedFired = false; - wizard.IsRunningChanged += (s, e) => { isRunningChangedFired = true; }; - - sessionToken = Application.Begin (wizard); - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (step1.Title, wizard.CurrentStep.Title); - wizard.NextFinishButton.InvokeCommand (Command.Accept); - Assert.False (finishedFired); - Assert.False (isRunningChangedFired); - - Assert.Equal (step2.Title, wizard.CurrentStep.Title); - Assert.Equal (wizard.GetLastStep ().Title, wizard.CurrentStep.Title); - wizard.NextFinishButton.InvokeCommand (Command.Accept); - Application.End (sessionToken); - Assert.True (finishedFired); - Assert.True (isRunningChangedFired); - - step1.Dispose (); - step2.Dispose (); - wizard.Dispose (); - - // Same test, but with two steps but the 1st one disabled - wizard = new (); - step1 = new () { Title = "step1" }; - wizard.AddStep (step1); - step2 = new () { Title = "step2" }; - wizard.AddStep (step2); - step1.Enabled = false; - - finishedFired = false; - wizard.Accepting += (s, args) => { finishedFired = true; }; - - isRunningChangedFired = false; - wizard.IsRunningChanged += (s, e) => { isRunningChangedFired = true; }; - - sessionToken = Application.Begin (wizard); - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (step2.Title, wizard.CurrentStep.Title); - Assert.Equal (wizard.GetLastStep ().Title, wizard.CurrentStep.Title); - wizard.NextFinishButton.InvokeCommand (Command.Accept); - Application.End (sessionToken); - Assert.True (finishedFired); - Assert.True (isRunningChangedFired); - wizard.Dispose (); - } - - [Fact] - public void Navigate_GetFirstStep_Works () - { - var wizard = new Wizard (); - - Assert.Null (wizard.GetFirstStep ()); - - var step1 = new WizardStep { Title = "step1" }; - wizard.AddStep (step1); - Assert.Equal (step1.Title, wizard.GetFirstStep ().Title); - - var step2 = new WizardStep { Title = "step2" }; - wizard.AddStep (step2); - Assert.Equal (step1.Title, wizard.GetFirstStep ().Title); - - var step3 = new WizardStep { Title = "step3" }; - wizard.AddStep (step3); - Assert.Equal (step1.Title, wizard.GetFirstStep ().Title); - - step1.Enabled = false; - Assert.Equal (step2.Title, wizard.GetFirstStep ().Title); - - step1.Enabled = true; - Assert.Equal (step1.Title, wizard.GetFirstStep ().Title); - - step1.Enabled = false; - step2.Enabled = false; - Assert.Equal (step3.Title, wizard.GetFirstStep ().Title); - wizard.Dispose (); - } - - [Fact] - public void Navigate_GetLastStep_Works () - { - var wizard = new Wizard (); - - Assert.Null (wizard.GetLastStep ()); - - var step1 = new WizardStep { Title = "step1" }; - wizard.AddStep (step1); - Assert.Equal (step1.Title, wizard.GetLastStep ().Title); - - var step2 = new WizardStep { Title = "step2" }; - wizard.AddStep (step2); - Assert.Equal (step2.Title, wizard.GetLastStep ().Title); - - var step3 = new WizardStep { Title = "step3" }; - wizard.AddStep (step3); - Assert.Equal (step3.Title, wizard.GetLastStep ().Title); - - step3.Enabled = false; - Assert.Equal (step2.Title, wizard.GetLastStep ().Title); - - step3.Enabled = true; - Assert.Equal (step3.Title, wizard.GetLastStep ().Title); - - step3.Enabled = false; - step2.Enabled = false; - Assert.Equal (step1.Title, wizard.GetLastStep ().Title); - wizard.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Navigate_GetNextStep_Correct () - { - var wizard = new Wizard (); - - // If no steps should be null - Assert.Null (wizard.GetNextStep ()); - - var step1 = new WizardStep { Title = "step1" }; - wizard.AddStep (step1); - - // If no current step, should be first step - Assert.Equal (step1.Title, wizard.GetNextStep ().Title); - - wizard.CurrentStep = step1; - - // If there is 1 step it's current step should be null - Assert.Null (wizard.GetNextStep ()); - - // If one disabled step should be null - step1.Enabled = false; - Assert.Null (wizard.GetNextStep ()); - - // If two steps and at 1 and step 2 is `Enabled = true`should be step 2 - var step2 = new WizardStep { Title = "step2" }; - wizard.AddStep (step2); - Assert.Equal (step2.Title, wizard.GetNextStep ().Title); - - // If two steps and at 1 and step 2 is `Enabled = false` should be null - step1.Enabled = true; - wizard.CurrentStep = step1; - step2.Enabled = false; - Assert.Null (wizard.GetNextStep ()); - - // If three steps with Step2.Enabled = true - // At step 1 should be step 2 - // At step 2 should be step 3 - // At step 3 should be null - var step3 = new WizardStep { Title = "step3" }; - wizard.AddStep (step3); - step1.Enabled = true; - wizard.CurrentStep = step1; - step2.Enabled = true; - step3.Enabled = true; - Assert.Equal (step2.Title, wizard.GetNextStep ().Title); - wizard.CurrentStep = step2; - Assert.Equal (step3.Title, wizard.GetNextStep ().Title); - wizard.CurrentStep = step3; - Assert.Null (wizard.GetNextStep ()); - - // If three steps with Step2.Enabled = false - // At step 1 should be step 3 - // At step 3 should be null - step1.Enabled = true; - wizard.CurrentStep = step1; - step2.Enabled = false; - step3.Enabled = true; - Assert.Equal (step3.Title, wizard.GetNextStep ().Title); - wizard.CurrentStep = step3; - Assert.Null (wizard.GetNextStep ()); - - // If three steps with Step2.Enabled = false & Step3.Enabled = false - // At step 1 should be null - step1.Enabled = true; - wizard.CurrentStep = step1; - step2.Enabled = false; - step3.Enabled = false; - Assert.Null (wizard.GetNextStep ()); - - // If no current step, GetNextStep provides equivalent to GetFirstStep - wizard.CurrentStep = null; - step1.Enabled = true; - step2.Enabled = true; - step3.Enabled = true; - Assert.Equal (step1.Title, wizard.GetNextStep ().Title); - - step1.Enabled = false; - step2.Enabled = true; - step3.Enabled = true; - Assert.Equal (step2.Title, wizard.GetNextStep ().Title); - - step1.Enabled = false; - step2.Enabled = false; - step3.Enabled = true; - Assert.Equal (step3.Title, wizard.GetNextStep ().Title); - - step1.Enabled = false; - step2.Enabled = true; - step3.Enabled = false; - Assert.Equal (step2.Title, wizard.GetNextStep ().Title); - - step1.Enabled = true; - step2.Enabled = false; - step3.Enabled = false; - Assert.Equal (step1.Title, wizard.GetNextStep ().Title); - wizard.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Navigate_GetPreviousStep_Correct () - { - var wizard = new Wizard (); - - // If no steps should be null - Assert.Null (wizard.GetPreviousStep ()); - - var step1 = new WizardStep { Title = "step1" }; - wizard.AddStep (step1); - - // If no current step, should be last step - Assert.Equal (step1.Title, wizard.GetPreviousStep ().Title); - - wizard.CurrentStep = step1; - - // If there is 1 step it's current step should be null - Assert.Null (wizard.GetPreviousStep ()); - - // If one disabled step should be null - step1.Enabled = false; - Assert.Null (wizard.GetPreviousStep ()); - - // If two steps and at 2 and step 1 is `Enabled = true`should be step1 - var step2 = new WizardStep { Title = "step2" }; - wizard.AddStep (step2); - wizard.CurrentStep = step2; - step1.Enabled = true; - Assert.Equal (step1.Title, wizard.GetPreviousStep ().Title); - - // If two steps and at 2 and step 1 is `Enabled = false` should be null - step1.Enabled = false; - Assert.Null (wizard.GetPreviousStep ()); - - // If three steps with Step2.Enabled = true - // At step 1 should be null - // At step 2 should be step 1 - // At step 3 should be step 2 - var step3 = new WizardStep { Title = "step3" }; - wizard.AddStep (step3); - step1.Enabled = true; - wizard.CurrentStep = step1; - step2.Enabled = true; - step3.Enabled = true; - Assert.Null (wizard.GetPreviousStep ()); - wizard.CurrentStep = step2; - Assert.Equal (step1.Title, wizard.GetPreviousStep ().Title); - wizard.CurrentStep = step3; - Assert.Equal (step2.Title, wizard.GetPreviousStep ().Title); - - // If three steps with Step2.Enabled = false - // At step 1 should be null - // At step 3 should be step1 - step1.Enabled = true; - step2.Enabled = false; - step3.Enabled = true; - wizard.CurrentStep = step1; - Assert.Null (wizard.GetPreviousStep ()); - wizard.CurrentStep = step3; - Assert.Equal (step1.Title, wizard.GetPreviousStep ().Title); - - // If three steps with Step1.Enabled = false & Step2.Enabled = false - // At step 3 should be null - - // If no current step, GetPreviousStep provides equivalent to GetLastStep - wizard.CurrentStep = null; - step1.Enabled = true; - step2.Enabled = true; - step3.Enabled = true; - Assert.Equal (step3.Title, wizard.GetPreviousStep ().Title); - - step1.Enabled = false; - step2.Enabled = true; - step3.Enabled = true; - Assert.Equal (step3.Title, wizard.GetPreviousStep ().Title); - - step1.Enabled = false; - step2.Enabled = false; - step3.Enabled = true; - Assert.Equal (step3.Title, wizard.GetPreviousStep ().Title); - - step1.Enabled = false; - step2.Enabled = true; - step3.Enabled = false; - Assert.Equal (step2.Title, wizard.GetPreviousStep ().Title); - - step1.Enabled = true; - step2.Enabled = false; - step3.Enabled = false; - Assert.Equal (step1.Title, wizard.GetPreviousStep ().Title); - wizard.Dispose (); - } - - [Fact] - public void Navigate_GoBack_Works () - { - // If zero steps do nothing - - // If one step do nothing (enabled or disabled) - - // If two steps - // If current is 1 does nothing - // If current is 2 does nothing - // If 1 is enabled 2 becomes current - // If 1 is disabled 1 stays current - } - - [Fact] - public void Navigate_GoNext_Works () - { - // If zero steps do nothing - - // If one step do nothing (enabled or disabled) - - // If two steps - // If current is 1 - // If 2 is enabled 2 becomes current - // If 2 is disabled 1 stays current - // If current is 2 does nothing - } - - [Fact] - [AutoInitShutdown] - - // This test verifies that a single step wizard shows the correct buttons - // and that the title is correct - public void OneStepWizard_Shows () - { - var title = "1234"; - var stepTitle = "ABCD"; - - var width = 30; - var height = 7; - Application.Driver?.SetScreenSize (width, height); - - // var btnBackText = "Back"; - var btnBack = string.Empty; // $"{Glyphs.LeftBracket} {btnBackText} {Glyphs.RightBracket}"; - var btnNextText = "Finish"; // "Next"; - - var btnNext = - $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} {btnNextText} {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}"; - - var topRow = - $"{Glyphs.ULCornerDbl}╡{title} - {stepTitle}╞{new (Glyphs.HLineDbl.ToString () [0], width - title.Length - stepTitle.Length - 7)}{Glyphs.URCornerDbl}"; - var row2 = $"{Glyphs.VLineDbl}{new (' ', width - 2)}{Glyphs.VLineDbl}"; - string row3 = row2; - string row4 = row3; - - var separatorRow = - $"{Glyphs.VLineDbl}{new (Glyphs.HLine.ToString () [0], width - 2)}{Glyphs.VLineDbl}"; - - var buttonRow = - $"{Glyphs.VLineDbl}{btnBack}{new (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{Glyphs.VLineDbl}"; - - var bottomRow = - $"{Glyphs.LLCornerDbl}{new (Glyphs.HLineDbl.ToString () [0], width - 2)}{Glyphs.LRCornerDbl}"; - - Wizard wizard = new () { Title = title, Width = width, Height = height }; - wizard.AddStep (new () { Title = stepTitle }); - - //wizard.LayoutSubViews (); - SessionToken sessionToken = Application.Begin (wizard); - AutoInitShutdownAttribute.RunIteration (); - - // TODO: Disabled until Dim.Auto is used in Dialog - //DriverAsserts.AssertDriverContentsWithFrameAre ( - // $"{topRow}\n{row2}\n{row3}\n{row4}\n{separatorRow}\n{buttonRow}\n{bottomRow}", - // _output - // ); - Application.End (sessionToken); - wizard.Dispose (); - } - - [Fact] - [AutoInitShutdown] - - // this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle") - public void Setting_Title_Works () - { - IDriver d = Application.Driver; - - var title = "1234"; - var stepTitle = " - ABCD"; - - var width = 40; - var height = 4; - d.SetScreenSize (width, height); - - var btnNextText = "Finish"; - - var btnNext = - $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} {btnNextText} {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}"; - - var topRow = - $"{Glyphs.ULCornerDbl}╡{title}{stepTitle}╞{new (Glyphs.HLineDbl.ToString () [0], width - title.Length - stepTitle.Length - 4)}{Glyphs.URCornerDbl}"; - - var separatorRow = - $"{Glyphs.VLineDbl}{new (Glyphs.HLine.ToString () [0], width - 2)}{Glyphs.VLineDbl}"; - - // Once this is fixed, revert to commented out line: https://github.com/gui-cs/Terminal.Gui/issues/1791 - var buttonRow = - $"{Glyphs.VLineDbl}{new (' ', width - btnNext.Length - 2)}{btnNext}{Glyphs.VLineDbl}"; - - //var buttonRow = $"{Glyphs.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{Glyphs.VDLine}"; - var bottomRow = - $"{Glyphs.LLCornerDbl}{new (Glyphs.HLineDbl.ToString () [0], width - 2)}{Glyphs.LRCornerDbl}"; - - var wizard = new Wizard { Title = title, Width = width, Height = height }; - wizard.AddStep (new () { Title = "ABCD" }); - - Application.End (Application.Begin (wizard)); - wizard.Dispose (); - } - - [Fact] - - // This test verifies that the 2nd step in a wizard with more than 2 steps - // shows the correct buttons on all steps - // and that the title is correct - public void ThreeStepWizard_Next_Shows_Steps () - { - // verify step one - - // Next - - // verify step two - - // Back - - // verify step one again - } - - [Fact] - - // This test verifies that the 2nd step in a wizard with 2 steps - // shows the correct buttons on both steps - // and that the title is correct - public void TwoStepWizard_Next_Shows_SecondStep () - { - // verify step one - - // Next - - // verify step two - - // Back - - // verify step one again - } - - // =========== WizardStep Tests - - [Fact] - public void WizardStep_ButtonText () - { - // Verify default button text - - // Verify set actually changes property - - // Verify set actually changes buttons for the current step - } - - [Fact] - public void WizardStep_Set_Title_Fires_TitleChanged () - { - var r = new Window (); - Assert.Equal (string.Empty, r.Title); - - var expected = string.Empty; - r.TitleChanged += (s, args) => { Assert.Equal (r.Title, args.Value); }; - - expected = "title"; - r.Title = expected; - Assert.Equal (expected, r.Title); - - expected = "another title"; - r.Title = expected; - Assert.Equal (expected, r.Title); - r.Dispose (); - } - - [Fact] - public void WizardStep_Set_Title_Fires_TitleChanging () - { - var r = new Window (); - Assert.Equal (string.Empty, r.Title); - - var expectedAfter = string.Empty; - var expectedDuring = string.Empty; - var cancel = false; - - r.TitleChanging += (s, args) => - { - Assert.Equal (expectedDuring, args.NewValue); - args.Cancel = cancel; - }; - - r.Title = expectedDuring = expectedAfter = "title"; - Assert.Equal (expectedAfter, r.Title); - - r.Title = expectedDuring = expectedAfter = "a different title"; - Assert.Equal (expectedAfter, r.Title); - - // Now setup cancelling the change and change it back to "title" - cancel = true; - r.Title = expectedDuring = "title"; - Assert.Equal (expectedAfter, r.Title); - r.Dispose (); - } - - [Fact] - [AutoInitShutdown] - - // Verify a zero-step wizard doesn't crash and shows a blank wizard - // and that the title is correct - public void ZeroStepWizard_Shows () - { - var title = "1234"; - var stepTitle = ""; - - var width = 30; - var height = 6; - Application.Driver?.SetScreenSize (width, height); - - var btnBackText = "Back"; - var btnBack = $"{Glyphs.LeftBracket} {btnBackText} {Glyphs.RightBracket}"; - var btnNextText = "Finish"; - - var btnNext = - $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} {btnNextText} {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}"; - - var topRow = - $"{Glyphs.ULCornerDbl}╡{title}{stepTitle}╞{new (Glyphs.HLineDbl.ToString () [0], width - title.Length - stepTitle.Length - 4)}{Glyphs.URCornerDbl}"; - var row2 = $"{Glyphs.VLineDbl}{new (' ', width - 2)}{Glyphs.VLineDbl}"; - string row3 = row2; - - var separatorRow = - $"{Glyphs.VLineDbl}{new (Glyphs.HLine.ToString () [0], width - 2)}{Glyphs.VLineDbl}"; - - var buttonRow = - $"{Glyphs.VLineDbl}{btnBack}{new (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{Glyphs.VLineDbl}"; - - var bottomRow = - $"{Glyphs.LLCornerDbl}{new (Glyphs.HLineDbl.ToString () [0], width - 2)}{Glyphs.LRCornerDbl}"; - - var wizard = new Wizard { Title = title, Width = width, Height = height }; - SessionToken sessionToken = Application.Begin (wizard); - - // TODO: Disabled until Dim.Auto is used in Dialog - //DriverAsserts.AssertDriverContentsWithFrameAre ( - // $"{topRow}\n{row2}\n{row3}\n{separatorRow}\n{buttonRow}\n{bottomRow}", - // _output - // ); - Application.End (sessionToken); - wizard.Dispose (); - } -} diff --git a/Tests/UnitTests/FileServices/FileDialogTests.cs b/Tests/UnitTests/FileServices/FileDialogTests.cs deleted file mode 100644 index 9e11a0a1da..0000000000 --- a/Tests/UnitTests/FileServices/FileDialogTests.cs +++ /dev/null @@ -1,809 +0,0 @@ -using System.IO.Abstractions.TestingHelpers; -using System.Runtime.InteropServices; - -namespace UnitTests.FileServicesTests; - -public class FileDialogTests () -{ - [Theory] - [InlineData (true)] - [InlineData (false)] - [AutoInitShutdown] - public void CancelSelection (bool cancel) - { - FileDialog dlg = GetInitializedFileDialog (); - string openIn = Path.Combine (Environment.CurrentDirectory, "zz"); - Directory.CreateDirectory (openIn); - dlg.Path = openIn + Path.DirectorySeparatorChar; - - dlg.FilesSelected += (s, e) => e.Cancel = cancel; - - //pressing enter will complete the current selection - // unless the event cancels the confirm - Application.RaiseKeyDownEvent (Key.Enter); - - Assert.Equal (cancel, dlg.Canceled); - dlg.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void DirectTyping_Allowed () - { - FileDialog dlg = GetInitializedFileDialog (); - TextField tf = dlg.SubViews.OfType ().First (t => t.HasFocus); - tf.ClearAllSelection (); - tf.InsertionPoint = tf.Text.Length; - Assert.True (tf.HasFocus); - - SendSlash (); - - Assert.Equal ( - new DirectoryInfo (Environment.CurrentDirectory + Path.DirectorySeparatorChar).FullName, - new DirectoryInfo (dlg.Path + Path.DirectorySeparatorChar).FullName - ); - - // continue typing the rest of the path - Send ("Bob"); - Application.RaiseKeyDownEvent ('.'); - Send ("csv"); - - Assert.True (dlg.Canceled); - - Application.RaiseKeyDownEvent (Key.Enter); - Assert.False (dlg.Canceled); - Assert.Equal ("Bob.csv", Path.GetFileName (dlg.Path)); - dlg.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void DirectTyping_AutoComplete () - { - FileDialog dlg = GetInitializedFileDialog (); - string openIn = Path.Combine (Environment.CurrentDirectory, "zz"); - - Directory.CreateDirectory (openIn); - - string expectedDest = Path.Combine (openIn, "xx"); - Directory.CreateDirectory (expectedDest); - - dlg.Path = openIn + Path.DirectorySeparatorChar; - - Send ("x"); - - // nothing selected yet - Assert.True (dlg.Canceled); - Assert.Equal ("x", Path.GetFileName (dlg.Path)); - - // complete auto typing - Application.RaiseKeyDownEvent ('\t'); - - // but do not close dialog - Assert.True (dlg.Canceled); - Assert.EndsWith ("xx" + Path.DirectorySeparatorChar, dlg.Path); - - // press enter again to confirm the dialog - Application.RaiseKeyDownEvent (Key.Enter); - Assert.False (dlg.Canceled); - Assert.EndsWith ("xx" + Path.DirectorySeparatorChar, dlg.Path); - dlg.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void DoNotConfirmSelectionWhenFindFocused () - { - FileDialog dlg = GetInitializedFileDialog (); - string openIn = Path.Combine (Environment.CurrentDirectory, "zz"); - Directory.CreateDirectory (openIn); - dlg.Path = openIn + Path.DirectorySeparatorChar; - - var tf = dlg.SubViews.First (view => view.Id == "_tableViewContainer").SubViews.First (v => v.Id == "_tbFind") as TextField; - tf.SetFocus (); - - Assert.IsType (dlg.MostFocused); - Assert.Same (tf, dlg.MostFocused); - - Assert.Equal (Strings.cmdFind, tf.Title); - - // Dialog has not yet been confirmed with a choice - Assert.True (dlg.Canceled); - - //pressing enter while search focused should not confirm path - Application.RaiseKeyDownEvent (Key.Enter); - - Assert.True (dlg.Canceled); - - //// tabbing out of search - //Application.RaiseKeyDownEvent ('\t'); - - ////should allow enter to confirm path - //Application.RaiseKeyDownEvent (Key.Enter); - - //// Dialog has not yet been confirmed with a choice - //Assert.False (dlg.Canceled); - dlg.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void DotDot_MovesToRoot_ThenPressBack () - { - FileDialog dlg = GetDialog (); - dlg.OpenMode = OpenMode.Directory; - dlg.AllowsMultipleSelection = true; - var selected = false; - dlg.FilesSelected += (s, e) => { selected = true; }; - - AssertIsTheStartingDirectory (dlg.Path); - - Assert.IsType (dlg.MostFocused); - Application.RaiseKeyDownEvent (Key.CursorDown); - - var tv = GetTableView(dlg); - tv.SetFocus (); - - Assert.IsType (dlg.MostFocused); - - // ".." should be the first thing selected - // ".." should not mess with the displayed path - AssertIsTheStartingDirectory (dlg.Path); - - // Accept navigation up a directory - Application.RaiseKeyDownEvent (Key.Enter); - - AssertIsTheRootDirectory (dlg.Path); - - Assert.True (dlg.Canceled); - Assert.False (selected); - - Assert.IsType (dlg.MostFocused); - - // Now press Backspace (in table view) - Application.RaiseKeyDownEvent (Key.Backspace); - - // Should move us back to the root - AssertIsTheStartingDirectory (dlg.Path); - - Assert.True (dlg.Canceled); - Assert.False (selected); - dlg.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (true)] - [InlineData (false)] - public void MultiSelectDirectory_CannotToggleDotDot (bool acceptWithEnter) - { - FileDialog dlg = GetDialog (); - dlg.OpenMode = OpenMode.Directory; - dlg.AllowsMultipleSelection = true; - IReadOnlyCollection eventMultiSelected = null; - dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; }; - - var tv = GetTableView (dlg); - tv.SetFocus (); - - Assert.IsType (dlg.MostFocused); - - // Try to toggle '..' - Application.RaiseKeyDownEvent (' '); - Application.RaiseKeyDownEvent (Key.CursorDown); - - // Toggle subfolder - Application.RaiseKeyDownEvent (' '); - - Assert.True (dlg.Canceled); - - if (acceptWithEnter) - { - Application.RaiseKeyDownEvent (Key.Enter); - } - else - { - Application.RaiseKeyDownEvent (Key.O.WithAlt); - } - - Assert.False (dlg.Canceled); - - Assert.Multiple ( - () => - { - // Only the subfolder should be selected - Assert.Single (dlg.MultiSelected); - AssertIsTheSubfolder (dlg.Path); - AssertIsTheSubfolder (dlg.MultiSelected.Single ()); - }, - () => - { - // Event should also agree with the final state - Assert.NotNull (eventMultiSelected); - Assert.Single (eventMultiSelected); - AssertIsTheSubfolder (eventMultiSelected.Single ()); - } - ); - dlg.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (true)] - [InlineData (false)] - public void MultiSelectDirectory_CanToggleThenAccept (bool acceptWithEnter) - { - FileDialog dlg = GetDialog (); - dlg.OpenMode = OpenMode.Directory; - dlg.AllowsMultipleSelection = true; - IReadOnlyCollection eventMultiSelected = null; - dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; }; - - var tv = GetTableView (dlg); - tv.SetFocus (); - - Assert.IsType (dlg.MostFocused); - - // Move selection to subfolder - Application.RaiseKeyDownEvent (Key.CursorDown); - - // Toggle subfolder - Application.RaiseKeyDownEvent (' '); - - Assert.True (dlg.Canceled); - - if (acceptWithEnter) - { - Application.RaiseKeyDownEvent (Key.Enter); - } - else - { - Application.RaiseKeyDownEvent (Key.O.WithAlt); - } - - Assert.False (dlg.Canceled); - - Assert.Multiple ( - () => - { - // Only the subfolder should be selected - Assert.Single (dlg.MultiSelected); - AssertIsTheSubfolder (dlg.Path); - AssertIsTheSubfolder (dlg.MultiSelected.Single ()); - }, - () => - { - // Event should also agree with the final state - Assert.NotNull (eventMultiSelected); - Assert.Single (eventMultiSelected); - AssertIsTheSubfolder (eventMultiSelected.Single ()); - } - ); - dlg.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void MultiSelectDirectory_EnterOpensFolder () - { - FileDialog dlg = GetDialog (); - dlg.OpenMode = OpenMode.Directory; - dlg.AllowsMultipleSelection = true; - IReadOnlyCollection eventMultiSelected = null; - dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; }; - - var tv = GetTableView (dlg); - tv.SetFocus (); - - Assert.IsType (dlg.MostFocused); - - // Move selection to subfolder - Application.RaiseKeyDownEvent (Key.CursorDown); - - Application.RaiseKeyDownEvent (Key.Enter); - - // Path should update to the newly opened folder - AssertIsTheSubfolder (dlg.Path); - - // No selection will have been confirmed - Assert.True (dlg.Canceled); - Assert.Empty (dlg.MultiSelected); - Assert.Null (eventMultiSelected); - dlg.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void OnLoad_TextBoxIsFocused () - { - FileDialog dlg = GetInitializedFileDialog (); - - View tf = dlg.SubViews.FirstOrDefault (t => t.HasFocus); - Assert.NotNull (tf); - Assert.IsType (tf); - dlg.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (true, true)] - [InlineData (true, false)] - [InlineData (false, true)] - [InlineData (false, false)] - public void PickDirectory_ArrowNavigation (bool openModeMixed, bool multiple) - { - FileDialog dlg = GetDialog (); - dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory; - dlg.AllowsMultipleSelection = multiple; - - var tv = GetTableView (dlg); - tv.SetFocus (); - - Assert.IsType (dlg.MostFocused); - - // Should be selecting .. - Application.RaiseKeyDownEvent (Key.CursorDown); - - // Down to the directory - Assert.True (dlg.Canceled); - - // Alt+O to open (enter would just navigate into the child dir) - Application.RaiseKeyDownEvent (Key.O.WithAlt); - Assert.False (dlg.Canceled); - - AssertIsTheSubfolder (dlg.Path); - dlg.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (true, true)] - [InlineData (true, false)] - [InlineData (false, true)] - [InlineData (false, false)] - public void PickDirectory_DirectTyping (bool openModeMixed, bool multiple) - { - FileDialog dlg = GetDialog (); - dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory; - dlg.AllowsMultipleSelection = multiple; - - // whe first opening the text field will have select all on - // so to add to current path user must press End or right - Application.RaiseKeyDownEvent (Key.CursorLeft); - Application.RaiseKeyDownEvent (Key.CursorRight); - - Send ("subfolder"); - - // Dialog has not yet been confirmed with a choice - Assert.True (dlg.Canceled); - - // Now it has - Application.RaiseKeyDownEvent (Key.Enter); - Assert.False (dlg.Canceled); - AssertIsTheSubfolder (dlg.Path); - dlg.Dispose (); - } - - [Theory] - [InlineData (".csv", null, false)] - [InlineData (".csv", "", false)] - [InlineData (".csv", "c:\\MyFile.csv", true)] - [InlineData (".csv", "c:\\MyFile.CSV", true)] - [InlineData (".csv", "c:\\MyFile.csv.bak", false)] - public void TestAllowedType_Basic (string allowed, string candidate, bool expected) - { - Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate)); - } - - [Theory] - [InlineData (".Designer.cs", "c:\\MyView.Designer.cs", true)] - [InlineData (".Designer.cs", "c:\\temp/MyView.Designer.cs", true)] - [InlineData (".Designer.cs", "MyView.Designer.cs", true)] - [InlineData (".Designer.cs", "c:\\MyView.DESIGNER.CS", true)] - [InlineData (".Designer.cs", "MyView.cs", false)] - public void TestAllowedType_DoubleBarreled (string allowed, string candidate, bool expected) - { - Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate)); - } - - [Theory] - [InlineData ("Dockerfile", "c:\\temp\\Dockerfile", true)] - [InlineData ("Dockerfile", "Dockerfile", true)] - [InlineData ("Dockerfile", "someimg.Dockerfile", true)] - public void TestAllowedType_SpecificFile (string allowed, string candidate, bool expected) - { - Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate)); - } - - [Fact] - [AutoInitShutdown] - public void TestDirectoryContents_Linux () - { - if (IsWindows ()) - { - return; - } - - FileDialog fd = GetLinuxDialog (); - fd.Title = string.Empty; - - fd.Style.Culture = new ("en-US"); - - fd.Draw (); - - /* - * - * - ┌─────────────────────────────────────────────────────────────────────────┐ - │/demo/ │ - │⟦▲⟧ │ - │┌────────────┬──────────┬──────────────────────────────┬────────────────┐│ - ││Filename (▲)│Size │Modified │Type ││ - │├────────────┼──────────┼──────────────────────────────┼────────────────┤│ - ││.. │ │ │ ││ - ││/subfolder │ │2002-01-01T22:42:10 │ ││ - ││image.gif │4.00 B │2002-01-01T22:42:10 │.gif ││ - ││jQuery.js │7.00 B │2001-01-01T11:44:42 │.js ││ - │ │ - │ │ - │ │ - │⟦ ►► ⟧ Enter Search ⟦► OK ◄⟧ ⟦ Cancel ⟧ │ - └─────────────────────────────────────────────────────────────────────────┘ - - * - */ - - var path = fd.SubViews.OfType ().ElementAt (0); - Assert.Equal ("/demo/", path.Text); - - var tv = GetTableView (fd); - - // Asserting the headers - Assert.Equal ("Filename (▲)", tv.Table.ColumnNames.ElementAt (0)); - Assert.Equal ("Size", tv.Table.ColumnNames.ElementAt (1)); - Assert.Equal ("Modified", tv.Table.ColumnNames.ElementAt (2)); - Assert.Equal ("Type", tv.Table.ColumnNames.ElementAt (3)); - - // Asserting the table contents - Assert.Equal ("..", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [0, 0])); - Assert.Equal ("/subfolder", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [1, 0])); - Assert.Equal ("image.gif", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [2, 0])); - Assert.Equal ("jQuery.js", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [3, 0])); - - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [0, 1])); - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [1, 1])); - Assert.Equal ("4.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [2, 1])); - Assert.Equal ("7.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [3, 1])); - - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [0, 2])); - Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [1, 2])); - Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [2, 2])); - Assert.Equal ("2001-01-01T11:44:42", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [3, 2])); - - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [0, 3])); - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [1, 3])); - Assert.Equal (".gif", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [2, 3])); - Assert.Equal (".js", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [3, 3])); - - fd.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TestDirectoryContents_Windows () - { - if (!IsWindows ()) - { - return; - } - - FileDialog fd = GetWindowsDialog (); - fd.Title = string.Empty; - - fd.Style.Culture = new ("en-US"); - - fd.Draw (); - - /* - * - * - ┌─────────────────────────────────────────────────────────────────────────┐ - │c:\demo\ │ - │⟦▲⟧ │ - │┌────────────┬──────────┬──────────────────────────────┬────────────────┐│ - ││Filename (▲)│Size │Modified │Type ││ - │├────────────┼──────────┼──────────────────────────────┼────────────────┤│ - ││.. │ │ │ ││ - ││\subfolder │ │2002-01-01T22:42:10 │ ││ - ││image.gif │4.00 B │2002-01-01T22:42:10 │.gif ││ - ││jQuery.js │7.00 B │2001-01-01T11:44:42 │.js ││ - ││mybinary.exe│7.00 B │2001-01-01T11:44:42 │.exe ││ - │ │ - │ │ - │⟦ ►► ⟧ Enter Search ⟦► OK ◄⟧ ⟦ Cancel ⟧ │ - └─────────────────────────────────────────────────────────────────────────┘ - - * - */ - - var path = fd.SubViews.OfType ().ElementAt (0); - Assert.Equal ("c:\\demo\\",path.Text); - - var tv = GetTableView (fd); - - // Asserting the headers - Assert.Equal ("Filename (▲)", tv.Table.ColumnNames.ElementAt (0)); - Assert.Equal ("Size", tv.Table.ColumnNames.ElementAt (1)); - Assert.Equal ("Modified", tv.Table.ColumnNames.ElementAt (2)); - Assert.Equal ("Type", tv.Table.ColumnNames.ElementAt (3)); - - // Asserting the table contents - Assert.Equal ("..", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [0, 0])); - Assert.Equal (@"\subfolder", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [1, 0])); - Assert.Equal ("image.gif", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [2, 0])); - Assert.Equal ("jQuery.js", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [3, 0])); - Assert.Equal ("mybinary.exe", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [4, 0])); - - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [0, 1])); - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [1, 1])); - Assert.Equal ("4.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [2, 1])); - Assert.Equal ("7.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [3, 1])); - Assert.Equal ("7.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [4, 1])); - - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [0, 2])); - Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [1, 2])); - Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [2, 2])); - Assert.Equal ("2001-01-01T11:44:42", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [3, 2])); - Assert.Equal ("2001-01-01T11:44:42", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [4, 2])); - - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [0, 3])); - Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [1, 3])); - Assert.Equal (".gif", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [2, 3])); - Assert.Equal (".js", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [3, 3])); - Assert.Equal (".exe", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [4, 3])); - - fd.Dispose (); - } - - private void AssertIsTheRootDirectory (string path) - { - if (IsWindows ()) - { - Assert.Equal (@"c:\", path); - } - else - { - Assert.Equal ("/", path); - } - } - - private void AssertIsTheStartingDirectory (string path) - { - if (IsWindows ()) - { - Assert.Equal (@"c:\demo\", path); - } - else - { - Assert.Equal ("/demo/", path); - } - } - - private void AssertIsTheSubfolder (string path) - { - if (IsWindows ()) - { - Assert.Equal (@"c:\demo\subfolder", path); - } - else - { - Assert.Equal ("/demo/subfolder", path); - } - } - - private void Begin (FileDialog dlg) - { - dlg.BeginInit (); - dlg.EndInit (); - Application.Begin (dlg); - } - - private FileDialog GetDialog () { return IsWindows () ? GetWindowsDialog () : GetLinuxDialog (); } - - private FileDialog GetInitializedFileDialog () - { - - Window.DefaultBorderStyle = LineStyle.Single; - Dialog.DefaultButtonAlignment = Alignment.Center; - Dialog.DefaultBorderStyle = LineStyle.Single; - Dialog.DefaultShadow = ShadowStyles.None; - Button.DefaultShadow = ShadowStyles.None; - - var dlg = new FileDialog (); - Begin (dlg); - - return dlg; - } - - private FileDialog GetLinuxDialog () - { - Window.DefaultBorderStyle = LineStyle.Single; - Dialog.DefaultButtonAlignment = Alignment.Center; - Dialog.DefaultBorderStyle = LineStyle.Single; - Dialog.DefaultShadow = ShadowStyles.None; - Button.DefaultShadow = ShadowStyles.None; - - // Arrange - var fileSystem = new MockFileSystem (new Dictionary (), "/"); - fileSystem.MockTime (() => new (2010, 01, 01, 11, 12, 43)); - - fileSystem.AddFile ( - @"/myfile.txt", - new ("Testing is meh.") { LastWriteTime = new DateTime (2001, 01, 01, 11, 12, 11) } - ); - - fileSystem.AddFile ( - @"/demo/jQuery.js", - new ("some js") { LastWriteTime = new DateTime (2001, 01, 01, 11, 44, 42) } - ); - - fileSystem.AddFile ( - @"/demo/image.gif", - new (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) - { - LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) - } - ); - - var m = (MockDirectoryInfo)fileSystem.DirectoryInfo.New (@"/demo/subfolder"); - m.Create (); - m.LastWriteTime = new (2002, 01, 01, 22, 42, 10); - - fileSystem.AddFile ( - @"/demo/subfolder/image2.gif", - new (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) - { - LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) - } - ); - - var fd = new FileDialog (fileSystem) { Height = 15, Width = 75 }; - fd.Path = @"/demo/"; - Begin (fd); - - return fd; - } - - private FileDialog GetWindowsDialog () - { - // Override CM - Window.DefaultBorderStyle = LineStyle.Single; - Dialog.DefaultButtonAlignment = Alignment.Center; - Dialog.DefaultBorderStyle = LineStyle.Single; - Dialog.DefaultShadow = ShadowStyles.None; - Button.DefaultShadow = ShadowStyles.None; - - // Arrange - var fileSystem = new MockFileSystem (new Dictionary (), @"c:\"); - fileSystem.MockTime (() => new (2010, 01, 01, 11, 12, 43)); - - fileSystem.AddFile ( - @"c:\myfile.txt", - new ("Testing is meh.") { LastWriteTime = new DateTime (2001, 01, 01, 11, 12, 11) } - ); - - fileSystem.AddFile ( - @"c:\demo\jQuery.js", - new ("some js") { LastWriteTime = new DateTime (2001, 01, 01, 11, 44, 42) } - ); - - fileSystem.AddFile ( - @"c:\demo\mybinary.exe", - new ("some js") { LastWriteTime = new DateTime (2001, 01, 01, 11, 44, 42) } - ); - - fileSystem.AddFile ( - @"c:\demo\image.gif", - new (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) - { - LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) - } - ); - - var m = (MockDirectoryInfo)fileSystem.DirectoryInfo.New (@"c:\demo\subfolder"); - m.Create (); - m.LastWriteTime = new (2002, 01, 01, 22, 42, 10); - - fileSystem.AddFile ( - @"c:\demo\subfolder\image2.gif", - new (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) - { - LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) - } - ); - - var fd = new FileDialog (fileSystem) { Height = 15, Width = 75 }; - fd.Path = @"c:\demo\"; - Begin (fd); - - return fd; - } - /* - [Fact, AutoInitShutdown] - public void Autocomplete_NoSuggestion_WhenTextMatchesExactly () - { - var tb = new TextFieldWithAppendAutocomplete (); - ForceFocus (tb); - - tb.Text = "/bob/fish"; - tb.InsertionPoint = tb.Text.Length; - tb.GenerateSuggestions (null, "fish", "fishes"); - - // should not report success for autocompletion because we already have that exact - // string - Assert.False (tb.AcceptSelectionIfAny ()); - } - - [Fact, AutoInitShutdown] - public void Autocomplete_AcceptSuggstion () - { - var tb = new TextFieldWithAppendAutocomplete (); - ForceFocus (tb); - - tb.Text = @"/bob/fi"; - tb.InsertionPoint = tb.Text.Length; - tb.GenerateSuggestions (null, "fish", "fishes"); - - Assert.True (tb.AcceptSelectionIfAny ()); - Assert.Equal (@"/bob/fish", tb.Text); - }*/ - - private bool IsWindows () { return RuntimeInformation.IsOSPlatform (OSPlatform.Windows); } - - private void Send (string chars) - { - foreach (char ch in chars) - { - Application.RaiseKeyDownEvent (ch); - } - } - - private void SendSlash () - { - if (Path.DirectorySeparatorChar == '/') - { - Application.RaiseKeyDownEvent ('/'); - } - else - { - Application.RaiseKeyDownEvent ('\\'); - } - } - - private TableView GetTableView (FileDialog dlg) - { - // The table view is in the _tableViewContainer which is a direct subview of the dialog - // We need to search through all subviews recursively - TableView FindTableView (View view) - { - if (view is TableView tv) - { - return tv; - } - - foreach (View subview in view.SubViews) - { - TableView result = FindTableView (subview); - if (result != null) - { - return result; - } - } - - return null; - } - - return FindTableView (dlg); - } - -} diff --git a/Tests/UnitTests/FileServices/TreeViewFileSystemNavigationTests.cs b/Tests/UnitTests/FileServices/TreeViewFileSystemNavigationTests.cs deleted file mode 100644 index e4d918c1ad..0000000000 --- a/Tests/UnitTests/FileServices/TreeViewFileSystemNavigationTests.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System.IO.Abstractions; -using System.IO.Abstractions.TestingHelpers; -using UnitTests; - -namespace UnitTests.FileServicesTests; - -public class FileSystemCollectionNavigationMatcherTests -{ - [Fact] - [AutoInitShutdown] - public void TreeView_LetterBasedNavigation_WorksWithAspectGetter () - { - // Arrange - Create a mock file system with 3 files - var mockFileSystem = new MockFileSystem (new Dictionary ()); - mockFileSystem.AddFile ("apple.csv", new MockFileData ("")); - mockFileSystem.AddFile ("banana.csv", new MockFileData ("")); - mockFileSystem.AddFile ("cherry.csv", new MockFileData ("")); - - var apple = mockFileSystem.FileInfo.New ("apple.csv"); - var banana = mockFileSystem.FileInfo.New ("banana.csv"); - var cherry = mockFileSystem.FileInfo.New ("cherry.csv"); - - // Create TreeView with files - var treeView = new TreeView { Width = 20, Height = 10 }; - treeView.TreeBuilder = new DelegateTreeBuilder (_ => null); // No children - - // AspectGetter returns "[ICON] Name" format - treeView.AspectGetter = fsi => $"[ICON] {fsi.Name}"; - - // Use FileSystemCollectionNavigationMatcher which matches on Name property - treeView.KeystrokeNavigator.Matcher = new FileSystemCollectionNavigationMatcher (); - - treeView.AddObject (apple); - treeView.AddObject (banana); - treeView.AddObject (cherry); - - - // Act & Assert - // Select apple - treeView.SelectedObject = apple; - Assert.Equal (apple, treeView.SelectedObject); - - // Press 'b' - should navigate to banana (matches on Name, not AspectGetter result) - treeView.NewKeyDownEvent (Key.B); - Assert.Equal (banana, treeView.SelectedObject); - - // Press 'c' - should navigate to cherry - treeView.NewKeyDownEvent (Key.C); - Assert.Equal (cherry, treeView.SelectedObject); - - // Press 'a' - should cycle back to apple - treeView.NewKeyDownEvent (Key.A); - Assert.Equal (apple, treeView.SelectedObject); - - treeView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TreeView_LetterBasedNavigation_WithAspectGetter_NavigatesFromNonMatching () - { - // Arrange - var mockFileSystem = new MockFileSystem (new Dictionary ()); - mockFileSystem.AddFile ("apple.csv", new MockFileData ("")); - mockFileSystem.AddFile ("banana.csv", new MockFileData ("")); - mockFileSystem.AddFile ("cherry.csv", new MockFileData ("")); - mockFileSystem.AddFile ("date.csv", new MockFileData ("")); - - var apple = mockFileSystem.FileInfo.New ("apple.csv"); - var banana = mockFileSystem.FileInfo.New ("banana.csv"); - var cherry = mockFileSystem.FileInfo.New ("cherry.csv"); - var date = mockFileSystem.FileInfo.New ("date.csv"); - - var treeView = new TreeView { Width = 20, Height = 10 }; - treeView.TreeBuilder = new DelegateTreeBuilder (_ => null); - - // Even when the tree view has prefixes added by the AspectGetter - treeView.AspectGetter = fsi => $"[ICON] {fsi.Name}"; - - // The matcher should be able to access the model and make its own mind up if it matches - treeView.KeystrokeNavigator.Matcher = new FileSystemCollectionNavigationMatcher (); - - treeView.AddObject (apple); - treeView.AddObject (banana); - treeView.AddObject (cherry); - treeView.AddObject (date); - - // Act & Assert - // Select cherry (starts with 'c') - treeView.SelectedObject = cherry; - Assert.Equal (cherry, treeView.SelectedObject); - - // Press 'a' - should navigate to next 'a' item (wrapping around to apple) - treeView.NewKeyDownEvent (Key.A); - Assert.Equal (apple, treeView.SelectedObject); - - // Press 'c' - should navigate to next 'c' item (cherry) - treeView.NewKeyDownEvent (Key.C); - Assert.Equal (cherry, treeView.SelectedObject); - - // Press 'b' - from cherry (non-matching), should go to banana (next 'b' after cherry) - treeView.NewKeyDownEvent (Key.B); - Assert.Equal (banana, treeView.SelectedObject); - - treeView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TreeView_LetterBasedNavigation_MixedObjectTypes_FileSystemMatcherFallsBackToToString () - { - // Arrange - Mix file system objects with regular objects - var mockFileSystem = new MockFileSystem (new Dictionary ()); - mockFileSystem.AddFile ("apple.csv", new MockFileData ("")); - mockFileSystem.AddFile ("cherry.csv", new MockFileData ("")); - - var apple = mockFileSystem.FileInfo.New ("apple.csv"); - var cherry = mockFileSystem.FileInfo.New ("cherry.csv"); - - // Create TreeView that accepts any object type - var treeView = new TreeView { Width = 20, Height = 10 }; - treeView.TreeBuilder = new DelegateTreeBuilder (_ => null); - treeView.AspectGetter = obj => obj switch - { - IFileSystemInfo fsi => $"[FILE] {fsi.Name}", - _ => obj.ToString () - }; - - // Use FileSystemCollectionNavigationMatcher which handles both types - treeView.KeystrokeNavigator.Matcher = new FileSystemCollectionNavigationMatcher (); - - // Add mixed objects: file, string, file - treeView.AddObject (apple); - treeView.AddObject ("banana"); // Regular string object - treeView.AddObject (cherry); - - - // Act & Assert - // Select apple (file system object) - treeView.SelectedObject = apple; - Assert.Equal (apple, treeView.SelectedObject); - - // Press 'b' - should navigate to "banana" (string object, falls back to ToString) - treeView.NewKeyDownEvent (Key.B); - Assert.Equal ("banana", treeView.SelectedObject); - - // Press 'c' - should navigate to cherry (file system object) - treeView.NewKeyDownEvent (Key.C); - Assert.Equal (cherry, treeView.SelectedObject); - - // Press 'a' - should cycle back to apple - treeView.NewKeyDownEvent (Key.A); - Assert.Equal (apple, treeView.SelectedObject); - - // Press 'b' again - from apple, should go to banana - treeView.NewKeyDownEvent (Key.B); - Assert.Equal ("banana", treeView.SelectedObject); - - treeView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TreeView_LetterBasedNavigation_CustomAspectGetter_SearchUsesAspectNotToString () - { - // Arrange - Create objects where AspectGetter returns different values than ToString - var item1 = new TestItem { Id = 1, Name = "Zebra" }; // ToString = "1", AspectGetter = "Zebra" - var item2 = new TestItem { Id = 2, Name = "Apple" }; // ToString = "2", AspectGetter = "Apple" - var item3 = new TestItem { Id = 3, Name = "Banana" }; // ToString = "3", AspectGetter = "Banana" - - var treeView = new TreeView { Width = 20, Height = 10 }; - treeView.TreeBuilder = new DelegateTreeBuilder (_ => null); - - // AspectGetter returns Name property - treeView.AspectGetter = item => item.Name; - - // Use default matcher (not FileSystemCollectionNavigationMatcher) - // This should use ToString() on the collection items passed to it - // Since TreeView now passes the actual objects and uses AspectGetter to build the collection, - // the matcher will search based on the aspect (Name), not ToString (Id) - - treeView.AddObject (item1); // Zebra - treeView.AddObject (item2); // Apple - treeView.AddObject (item3); // Banana - - - // Act & Assert - // Select Zebra (item1) - treeView.SelectedObject = item1; - Assert.Equal (item1, treeView.SelectedObject); - - // Press 'a' - should navigate to Apple (based on Name via AspectGetter), NOT fail - // If it was using ToString(), 'a' wouldn't match anything (IDs are 1, 2, 3) - treeView.NewKeyDownEvent (Key.A); - Assert.Equal (item2, treeView.SelectedObject); // Should go to Apple - - // Press 'b' - should navigate to Banana - treeView.NewKeyDownEvent (Key.B); - Assert.Equal (item3, treeView.SelectedObject); // Should go to Banana - - // Press 'z' - should cycle to Zebra - treeView.NewKeyDownEvent (Key.Z); - Assert.Equal (item1, treeView.SelectedObject); // Should go to Zebra - - // Verify that pressing '1', '2', '3' (the ToString values) does NOT work - treeView.NewKeyDownEvent (Key.D1); - // Should stay on Zebra since no items start with '1' in their Name - Assert.Equal (item1, treeView.SelectedObject); - - treeView.Dispose (); - } - - private class TestItem - { - public int Id { get; set; } - public string Name { get; set; } - public override string ToString () => Id.ToString (); - } -} diff --git a/Tests/UnitTests/README.md b/Tests/UnitTests/README.md deleted file mode 100644 index db8d0451c6..0000000000 --- a/Tests/UnitTests/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Automated Unit Tests (non-Parallelizable) - -**IMPORTANT:** New tests belong in [UnitTests.Parallelizable](../UnitTestsParallelizable/README.md). Please use the [UnitTests.Parallelizable](../UnitTestsParallelizable/README.md) project for all new tests. - -See the [Testing wiki](https://github.com/gui-cs/Terminal.Gui/wiki/Testing) for details on how to add more tests. diff --git a/Tests/UnitTests/Text/AutocompleteTests.cs b/Tests/UnitTests/Text/AutocompleteTests.cs deleted file mode 100644 index 34272f33cb..0000000000 --- a/Tests/UnitTests/Text/AutocompleteTests.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System.Text.RegularExpressions; - -namespace UnitTests.TextTests; - -public class AutocompleteTests (ITestOutputHelper output) -{ - [Fact] - [AutoInitShutdown] - public void CursorLeft_CursorRight_Mouse_Button_Pressed_Does_Not_Show_Popup () - { - Application.Driver?.SetScreenSize (50, 5); - - var tv = new TextView { Width = 50, Height = 5, Text = "This a long line and against TextView." }; - - var g = (SingleWordSuggestionGenerator)tv.Autocomplete.SuggestionGenerator; - - g.AllSuggestions = Regex.Matches (tv.Text, "\\w+") - .Select (s => s.Value) - .Distinct () - .ToList (); - Runnable top = new (); - top.Add (tv); - SessionToken rs = Application.Begin (top); - - for (var i = 0; i < 7; i++) - { - Assert.True (tv.NewKeyDownEvent (Key.CursorRight)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (i + 1, tv.InsertionPoint.X); - Assert.Equal (i + 1, tv.Cursor.Position!.Value.X); - - if (i < 4 || i > 5) - { - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This a long line and against TextView.", - output - ); - } - else - { - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This a long line and against TextView. - and - against ", - output - ); - } - } - - Assert.True ( - tv.NewMouseEvent ( - new () { Position = new (6, 0), Flags = MouseFlags.LeftButtonPressed } - ) - ); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This a long line and against TextView. - and - against ", - output - ); - - Assert.True (tv.NewKeyDownEvent (Key.G)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This ag long line and against TextView. - against ", - output - ); - - Assert.True (tv.NewKeyDownEvent (Key.CursorLeft)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This ag long line and against TextView. - against ", - output - ); - - Assert.True (tv.NewKeyDownEvent (Key.CursorLeft)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This ag long line and against TextView. - against ", - output - ); - - Assert.True (tv.NewKeyDownEvent (Key.CursorLeft)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This ag long line and against TextView.", - output - ); - - for (var i = 0; i < 3; i++) - { - Assert.True (tv.NewKeyDownEvent (Key.CursorRight)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This ag long line and against TextView. - against ", - output - ); - } - - Assert.True (tv.NewKeyDownEvent (Key.Backspace)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This a long line and against TextView. - and - against ", - output - ); - - Assert.True (tv.NewKeyDownEvent (Key.N)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This an long line and against TextView. - and ", - output - ); - - Assert.True (tv.NewKeyDownEvent (Key.CursorRight)); - top.SetNeedsDraw (); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This an long line and against TextView.", - output - ); - Assert.Empty (tv.Autocomplete.Suggestions); - Assert.False (((PopupAutocomplete)tv.Autocomplete)._popup.Visible); - - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void KeyBindings_Command () - { - var tv = new TextView { Width = 10, Height = 2, Text = " Fortunately super feature." }; - Runnable top = new (); - top.Add (tv); - Application.Begin (top); - - Assert.Equal (Point.Empty, tv.InsertionPoint); - Assert.NotNull (tv.Autocomplete); - var g = (SingleWordSuggestionGenerator)tv.Autocomplete.SuggestionGenerator; - - Assert.Empty (g.AllSuggestions); - - g.AllSuggestions = Regex.Matches (tv.Text, "\\w+") - .Select (s => s.Value) - .Distinct () - .ToList (); - Assert.Equal (3, g.AllSuggestions.Count); - Assert.Equal ("Fortunately", g.AllSuggestions [0]); - Assert.Equal ("super", g.AllSuggestions [1]); - Assert.Equal ("feature", g.AllSuggestions [^1]); - Assert.Equal (0, tv.Autocomplete.SelectedIdx); - Assert.Empty (tv.Autocomplete.Suggestions); - Assert.True (tv.NewKeyDownEvent (Key.F.WithShift)); - top.Draw (); - Assert.Equal ("F Fortunately super feature.", tv.Text); - Assert.Equal (new (1, 0), tv.InsertionPoint); - Assert.Equal (2, tv.Autocomplete.Suggestions.Count); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [0].Replacement); - Assert.Equal ("feature", tv.Autocomplete.Suggestions [^1].Replacement); - Assert.Equal (0, tv.Autocomplete.SelectedIdx); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [tv.Autocomplete.SelectedIdx].Replacement); - Assert.True (tv.NewKeyDownEvent (Key.CursorDown)); - top.Draw (); - Assert.Equal ("F Fortunately super feature.", tv.Text); - Assert.Equal (new (1, 0), tv.InsertionPoint); - Assert.Equal (2, tv.Autocomplete.Suggestions.Count); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [0].Replacement); - Assert.Equal ("feature", tv.Autocomplete.Suggestions [^1].Replacement); - Assert.Equal (1, tv.Autocomplete.SelectedIdx); - Assert.Equal ("feature", tv.Autocomplete.Suggestions [tv.Autocomplete.SelectedIdx].Replacement); - Assert.True (tv.NewKeyDownEvent (Key.CursorDown)); - top.Draw (); - Assert.Equal ("F Fortunately super feature.", tv.Text); - Assert.Equal (new (1, 0), tv.InsertionPoint); - Assert.Equal (2, tv.Autocomplete.Suggestions.Count); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [0].Replacement); - Assert.Equal ("feature", tv.Autocomplete.Suggestions [^1].Replacement); - Assert.Equal (0, tv.Autocomplete.SelectedIdx); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [tv.Autocomplete.SelectedIdx].Replacement); - Assert.True (tv.NewKeyDownEvent (Key.CursorUp)); - top.Draw (); - Assert.Equal ("F Fortunately super feature.", tv.Text); - Assert.Equal (new (1, 0), tv.InsertionPoint); - Assert.Equal (2, tv.Autocomplete.Suggestions.Count); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [0].Replacement); - Assert.Equal ("feature", tv.Autocomplete.Suggestions [^1].Replacement); - Assert.Equal (1, tv.Autocomplete.SelectedIdx); - Assert.Equal ("feature", tv.Autocomplete.Suggestions [tv.Autocomplete.SelectedIdx].Replacement); - Assert.True (tv.NewKeyDownEvent (Key.CursorUp)); - top.Draw (); - Assert.Equal ("F Fortunately super feature.", tv.Text); - Assert.Equal (new (1, 0), tv.InsertionPoint); - Assert.Equal (2, tv.Autocomplete.Suggestions.Count); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [0].Replacement); - Assert.Equal ("feature", tv.Autocomplete.Suggestions [^1].Replacement); - Assert.Equal (0, tv.Autocomplete.SelectedIdx); - Assert.Equal ("Fortunately", tv.Autocomplete.Suggestions [tv.Autocomplete.SelectedIdx].Replacement); - Assert.True (tv.Autocomplete.Visible); - top.Draw (); - Assert.True (tv.NewKeyDownEvent (new (tv.Autocomplete.CloseKey))); - Assert.Equal ("F Fortunately super feature.", tv.Text); - Assert.Equal (new (1, 0), tv.InsertionPoint); - Assert.Empty (tv.Autocomplete.Suggestions); - Assert.Equal (3, g.AllSuggestions.Count); - Assert.False (tv.Autocomplete.Visible); - - Assert.True (tv.NewKeyDownEvent (new (tv.Autocomplete.Reopen))); - Assert.Equal ("F Fortunately super feature.", tv.Text); - Assert.Equal (new (1, 0), tv.InsertionPoint); - Assert.Equal (2, tv.Autocomplete.Suggestions.Count); - Assert.Equal (3, g.AllSuggestions.Count); - - Assert.True (tv.NewKeyDownEvent (new (tv.Autocomplete.SelectionKey))); - Assert.Equal ("Fortunately Fortunately super feature.", tv.Text); - Assert.Equal (new (11, 0), tv.InsertionPoint); - Assert.Empty (tv.Autocomplete.Suggestions); - Assert.Equal (3, g.AllSuggestions.Count); - Assert.False (tv.Autocomplete.Visible); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TestSettingSchemeOnAutocomplete () - { - var tv = new TextView (); - - // to begin with we should be using the default menu scheme - Assert.Same (SchemeManager.GetSchemes () ["Menu"], tv.Autocomplete.Scheme); - - // allocate a new custom scheme - tv.Autocomplete.Scheme = new () - { - Normal = new (Color.Black, Color.Blue), Focus = new (Color.Black, Color.Cyan) - }; - - // should be separate instance - Assert.NotSame (SchemeManager.GetSchemes () ["Menu"], tv.Autocomplete.Scheme); - - // with the values we set on it - Assert.Equal (new (Color.Black), tv.Autocomplete.Scheme.Normal.Foreground); - Assert.Equal (new (Color.Blue), tv.Autocomplete.Scheme.Normal.Background); - - Assert.Equal (new (Color.Black), tv.Autocomplete.Scheme.Focus.Foreground); - Assert.Equal (new (Color.Cyan), tv.Autocomplete.Scheme.Focus.Background); - } -} diff --git a/Tests/UnitTests/Tracing/ThreadSafeTraceTests.cs b/Tests/UnitTests/Tracing/ThreadSafeTraceTests.cs deleted file mode 100644 index ed7c1aa545..0000000000 --- a/Tests/UnitTests/Tracing/ThreadSafeTraceTests.cs +++ /dev/null @@ -1,352 +0,0 @@ -#nullable enable - -using Terminal.Gui.Tracing; - -#pragma warning disable xUnit1031 - -namespace UnitTests.TracingTests; - -/// -/// Tests for thread-safe tracing behavior. -/// These tests verify that tracing is properly isolated per-thread and per-async-context. -/// All tests work correctly in both Debug and Release builds. -/// IMPORTANT: These tests set static state on the Trace class, so they must be run in isolation (not parallelized) to -/// avoid interference between tests. -/// -public class ThreadSafeTraceTests (ITestOutputHelper output) -{ - [Fact] - public void EnabledCategories_Default_IsNone () - { - // Reset to clean state - Trace.EnabledCategories = TraceCategory.None; - - Assert.Equal (TraceCategory.None, Trace.EnabledCategories); - } - - [Fact] - public void EnabledCategories_CanBeSetAndRead () - { - try - { - Trace.EnabledCategories = TraceCategory.Command | TraceCategory.Mouse; - - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Mouse)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Keyboard)); - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public void EnabledCategories_HasFlagWorks () - { - try - { - Trace.EnabledCategories = TraceCategory.Command | TraceCategory.Keyboard; - - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Keyboard)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Mouse)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Navigation)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Lifecycle)); - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public void PushScope_RestoresPreviousState () - { - try - { - Trace.EnabledCategories = TraceCategory.None; - - using (Trace.PushScope (TraceCategory.Command)) - { - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - } - - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.Equal (TraceCategory.None, Trace.EnabledCategories); - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public void PushScope_NestedScopes_RestoreCorrectly () - { - try - { - Trace.EnabledCategories = TraceCategory.Mouse; - - using (Trace.PushScope (TraceCategory.Command)) - { - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Mouse)); - - using (Trace.PushScope (TraceCategory.Keyboard)) - { - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Keyboard)); - } - - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Keyboard)); - } - - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Mouse)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public void PushScope_WithBackend_RestoresBackend () - { - ListBackend customBackend = new (); - - try - { - Trace.Backend = new NullBackend (); - - using (Trace.PushScope (TraceCategory.Command, customBackend)) - { - Assert.Same (customBackend, Trace.Backend); - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - } - - Assert.IsType (Trace.Backend); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - Trace.Backend = new NullBackend (); - } - } - - [Fact] - public void PushScope_CapturesTraces () - { - ListBackend backend = new (); - - try - { - using (Trace.PushScope (TraceCategory.Command, backend)) - { - View view = new () { Id = "test" }; - Trace.Command (view, Command.Accept, CommandRouting.Direct, "TestPhase", "TestMessage"); - -#if DEBUG - Assert.Single (backend.Entries); - Assert.Equal (TraceCategory.Command, backend.Entries [0].Category); - Assert.Contains ("test", backend.Entries [0].Id); -#else - // In Release, [Conditional("DEBUG")] removes Trace.Command calls - Assert.Empty (backend.Entries); -#endif - } - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - Trace.Backend = new NullBackend (); - } - } - - [Fact] - public void TestLogging_Verbose_WithTraceCategories () - { - try - { - using (TestLogging.Verbose (output, TraceCategory.Command)) - { - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Mouse)); - - CheckBox checkbox = new () { Id = "checkbox" }; - checkbox.InvokeCommand (Command.Activate); - } - - // After scope, tracing should be disabled - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public async Task ParallelTests_IsolatedTracing_Test1 () - { - ListBackend backend = new (); - - try - { - using (Trace.PushScope (TraceCategory.Command, backend)) - { - // Simulate some async work - await Task.Delay (10, TestContext.Current.CancellationToken); - - View view = new () { Id = "parallel-test-1" }; - Trace.Command (view, Command.Accept, CommandRouting.Direct, "Test1"); - - await Task.Delay (10, TestContext.Current.CancellationToken); - -#if DEBUG - - // This test should only see its own traces - Assert.All (backend.Entries, entry => Assert.Contains ("parallel-test-1", entry.Id)); -#else - Assert.Empty (backend.Entries); -#endif - - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - } - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public async Task ParallelTests_IsolatedTracing_Test2 () - { - ListBackend backend = new (); - - try - { - using (Trace.PushScope (TraceCategory.Mouse, backend)) - { - // Simulate some async work - await Task.Delay (10, TestContext.Current.CancellationToken); - - View view = new () { Id = "parallel-test-2" }; - Trace.Mouse (view, MouseFlags.LeftButtonClicked, Point.Empty, "Test2"); - - await Task.Delay (10, TestContext.Current.CancellationToken); - -#if DEBUG - - // This test should only see its own traces - Assert.All (backend.Entries, entry => Assert.Contains ("parallel-test-2", entry.Id)); -#else - Assert.Empty (backend.Entries); -#endif - - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Mouse)); - Assert.False (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - } - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public void EnabledCategories_IsAsyncLocal_IsolatesAcrossExecutionContexts () - { - try - { - Trace.EnabledCategories = TraceCategory.Command; - - // Task.Run flows ExecutionContext by default, so the async-local value is visible in the task. - // The key isolation behavior is that changes in the task don't affect the parent context. - Task.Run (() => - { - // Task sees parent's value due to ExecutionContext flow - _ = Trace.EnabledCategories; - - // Set different categories in task context - Trace.EnabledCategories = TraceCategory.Mouse; - }, - TestContext.Current.CancellationToken) - .Wait (TestContext.Current.CancellationToken); - - // Parent context is unchanged despite modifications in the task - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.Equal (TraceCategory.Command, Trace.EnabledCategories); - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } - - [Fact] - public void Backend_IsAsyncLocal_IsolatedPerExecutionContext () - { - ListBackend mainBackend = new (); - - try - { - Trace.Backend = mainBackend; - - // Use Task.Run which may or may not flow ExecutionContext - Task.Run (() => - { - _ = Trace.Backend; - - // Set different backend in this context - Trace.Backend = new ListBackend (); - }, - TestContext.Current.CancellationToken) - .Wait (TestContext.Current.CancellationToken); - - // Main thread backend should be unchanged - Assert.Same (mainBackend, Trace.Backend); - } - finally - { - Trace.Backend = new NullBackend (); - } - } - - [Fact] - public async Task AsyncLocal_FlowsAcrossAwait () - { - ListBackend backend = new (); - - try - { - using (Trace.PushScope (TraceCategory.Command, backend)) - { - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - - // EnabledCategories should flow across await - await Task.Delay (1, TestContext.Current.CancellationToken); - - Assert.True (Trace.EnabledCategories.HasFlag (TraceCategory.Command)); - Assert.Same (backend, Trace.Backend); - - View view = new () { Id = "async-test" }; - Trace.Command (view, Command.Accept, CommandRouting.Direct, "AfterAwait"); - -#if DEBUG - Assert.Single (backend.Entries); -#else - Assert.Empty (backend.Entries); -#endif - } - } - finally - { - Trace.EnabledCategories = TraceCategory.None; - } - } -} diff --git a/Tests/UnitTests/UICatalog/RunnerTests.cs b/Tests/UnitTests/UICatalog/RunnerTests.cs deleted file mode 100644 index 9829bdb8c0..0000000000 --- a/Tests/UnitTests/UICatalog/RunnerTests.cs +++ /dev/null @@ -1,231 +0,0 @@ -#nullable enable - -// Claude - Opus 4.5 -using System.Runtime.InteropServices; -using System.Text.Json; -using Microsoft.Extensions.Logging; -using UICatalog; -using Timeout = System.Threading.Timeout; - -namespace UnitTests.UICatalog; - -public class RunnerTests (ITestOutputHelper output) : TestsAllViews -{ - [Fact] - public void Constructor_DoesNotSetRuntimeConfig () - { - // Arrange - clear any previous config - ConfigurationManager.RuntimeConfig = null; - - // Act - Runner _ = new (); - - // Assert - RuntimeConfig should be null or empty when no args provided - // This verifies the early return path in the constructor - Assert.True (string.IsNullOrEmpty (ConfigurationManager.RuntimeConfig)); - } - - [Fact] - public void SetRuntimeConfig_WithForceDriver_SetsRuntimeConfig () - { - // Arrange - ConfigurationManager.RuntimeConfig = null; - var driverName = "TestDriver"; - - // Act - Runner runner = new (); - runner.SetRuntimeConfig (driverName); - - // Assert - Assert.NotNull (ConfigurationManager.RuntimeConfig); - Assert.Contains ("Application.ForceDriver", ConfigurationManager.RuntimeConfig); - Assert.Contains (driverName, ConfigurationManager.RuntimeConfig); - - // Cleanup - ConfigurationManager.RuntimeConfig = null; - } - - [Fact] - public void SetRuntimeConfig_WithForce16Colors_SetsRuntimeConfig () - { - // Arrange - ConfigurationManager.RuntimeConfig = null; - - // Act - Runner runner = new (); - runner.SetRuntimeConfig (force16Colors: true); - - // Assert - Assert.NotNull (ConfigurationManager.RuntimeConfig); - Assert.Contains ("Driver.Force16Colors", ConfigurationManager.RuntimeConfig); - Assert.Contains ("true", ConfigurationManager.RuntimeConfig); - - // Cleanup - ConfigurationManager.RuntimeConfig = null; - } - - [Fact] - public void SetRuntimeConfig_WithBothOptions_SetsRuntimeConfig () - { - // Arrange - ConfigurationManager.RuntimeConfig = null; - - // Act - Runner runner = new (); - runner.SetRuntimeConfig ("TestDriver", false); - - // Assert - Assert.NotNull (ConfigurationManager.RuntimeConfig); - Assert.Contains ("Application.ForceDriver", ConfigurationManager.RuntimeConfig); - Assert.Contains ("Driver.Force16Colors", ConfigurationManager.RuntimeConfig); - - // Cleanup - ConfigurationManager.RuntimeConfig = null; - } - - [Fact] - public void SaveResultsToFile_WritesValidJson () - { - // Arrange - List results = - [ - new () { Scenario = "TestScenario1", Duration = TimeSpan.FromSeconds (1), IterationCount = 100 }, - new () { Scenario = "TestScenario2", Duration = TimeSpan.FromSeconds (2), IterationCount = 200 } - ]; - - string tempFile = Path.GetTempFileName (); - - try - { - // Act - Runner.SaveResultsToFile (results, tempFile); - - // Assert - Assert.True (File.Exists (tempFile)); - string content = File.ReadAllText (tempFile); - Assert.NotEmpty (content); - - // Verify it's valid JSON - List? deserialized = JsonSerializer.Deserialize> (content); - Assert.NotNull (deserialized); - Assert.Equal (2, deserialized.Count); - Assert.Equal ("TestScenario1", deserialized [0].Scenario); - Assert.Equal ("TestScenario2", deserialized [1].Scenario); - } - finally - { - // Cleanup - if (File.Exists (tempFile)) - { - File.Delete (tempFile); - } - } - } - - [Fact] - public void BenchmarkAllScenarios_EmptyList_ReturnsEmptyResults () - { - // Arrange - ConfigurationManager.RuntimeConfig = null; - Runner runner = new (); - List emptyScenarios = []; - - // Act - List results = runner.BenchmarkAllScenarios (emptyScenarios); - - // Assert - Assert.Empty (results); - } - - [Theory] - [InlineData ("Generic")] - public void RunScenario_WithValidScenario_MarksLogCaptureStart (string scenarioName) - { - // Skip on macOS due to timing issues - if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) - { - output.WriteLine ("Skipping on macOS due to timing issues."); - - return; - } - - // Arrange - ApplicationImpl.ResetModelUsageTracking (); - ConfigurationManager.Disable (true); - - // Clear log capture and add a pre-scenario log - global::UICatalog.UICatalog.LogCapture.Clear (); - ILogger logger = global::UICatalog.UICatalog.LogCapture.CreateLogger ("Test"); - logger.LogInformation ("Pre-scenario log"); - int preScenarioPosition = global::UICatalog.UICatalog.LogCapture.ScenarioStartPosition; - - ConfigurationManager.RuntimeConfig = null; - Runner runner = new (); - runner.SetRuntimeConfig (DriverRegistry.Names.ANSI); - - // We need to run the scenario in a way that it exits quickly - // The Generic scenario is a simple one that should work - - // Use a timer to force quit after a short time - Timer? quitTimer = null; - IApplication? currentApp; - - Application.InstanceInitialized += OnInstanceInitialized; - - try - { - // Act - runner.RunScenario (scenarioName, false); - - // Assert - the scenario start position should have been updated - Assert.True (global::UICatalog.UICatalog.LogCapture.ScenarioStartPosition > preScenarioPosition, - "MarkScenarioStart should have been called, updating the position"); - - // Scenario logs should not contain the pre-scenario log - string scenarioLogs = global::UICatalog.UICatalog.LogCapture.GetScenarioLogs (); - Assert.DoesNotContain ("Pre-scenario log", scenarioLogs); - } - finally - { - quitTimer?.Dispose (); - Application.InstanceInitialized -= OnInstanceInitialized; - ConfigurationManager.Disable (true); - ConfigurationManager.RuntimeConfig = null; - - // Reset model tracking so other tests can use either model - ApplicationImpl.ResetModelUsageTracking (); - } - - return; - - void OnInstanceInitialized (object? sender, EventArgs e) - { - currentApp = e.Value; - - quitTimer = new Timer (_ => - { - try - { - currentApp?.RequestStop (); - } - catch - { /* ignore */ - } - }, - null, - 500, - Timeout.Infinite); - } - } - - [Fact] - public void DisplayResultsUI_EmptyResults_ReturnsImmediately () - { - // Arrange - List emptyResults = []; - - // Act & Assert - should not throw and should return immediately - Exception? ex = Record.Exception (() => Runner.DisplayResultsUI (emptyResults)); - Assert.Null (ex); - } -} diff --git a/Tests/UnitTests/UICatalog/ScenarioLogCaptureTests.cs b/Tests/UnitTests/UICatalog/ScenarioLogCaptureTests.cs deleted file mode 100644 index 050a2edb49..0000000000 --- a/Tests/UnitTests/UICatalog/ScenarioLogCaptureTests.cs +++ /dev/null @@ -1,324 +0,0 @@ -#nullable enable - -// Claude - Opus 4.5 -using Microsoft.Extensions.Logging; -using UICatalog; - -namespace UnitTests.UICatalog; - -public class ScenarioLogCaptureTests (ITestOutputHelper output) -{ - private readonly ITestOutputHelper _output = output; - - [Fact] - public void Constructor_InitializesWithEmptyState () - { - // Arrange & Act - ScenarioLogCapture capture = new (); - - // Assert - Assert.False (capture.HasErrors); - Assert.Equal (0, capture.ScenarioStartPosition); - Assert.Equal (string.Empty, capture.GetScenarioLogs ()); - Assert.Equal (string.Empty, capture.GetAllLogs ()); - } - - [Fact] - public void CreateLogger_CapturesMessage () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - - // Act - logger.LogInformation ("Test message"); - - // Assert - string logs = capture.GetAllLogs (); - Assert.Contains ("[Information] Test message", logs); - } - - [Fact] - public void CreateLogger_MultipleMessages_CapturesAll () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - - // Act - logger.LogDebug ("Debug message"); - logger.LogInformation ("Info message"); - logger.LogWarning ("Warning message"); - - // Assert - string logs = capture.GetAllLogs (); - Assert.Contains ("[Debug] Debug message", logs); - Assert.Contains ("[Information] Info message", logs); - Assert.Contains ("[Warning] Warning message", logs); - } - - [Fact] - public void HasErrors_FalseForNonErrorLevels () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - - // Act - logger.LogTrace ("Trace"); - logger.LogDebug ("Debug"); - logger.LogInformation ("Info"); - logger.LogWarning ("Warning"); - - // Assert - Assert.False (capture.HasErrors); - } - - [Fact] - public void HasErrors_TrueForErrorLevel () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - - // Act - logger.LogError ("Error message"); - - // Assert - Assert.True (capture.HasErrors); - } - - [Fact] - public void HasErrors_TrueForCriticalLevel () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - - // Act - logger.LogCritical ("Critical message"); - - // Assert - Assert.True (capture.HasErrors); - } - - [Fact] - public void MarkScenarioStart_SetsPositionAndResetsErrors () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - logger.LogError ("Pre-scenario error"); - Assert.True (capture.HasErrors); - - // Act - capture.MarkScenarioStart (); - - // Assert - Assert.False (capture.HasErrors); - Assert.True (capture.ScenarioStartPosition > 0); - } - - [Fact] - public void GetScenarioLogs_ReturnsOnlyLogsSinceMark () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - logger.LogInformation ("Before mark"); - - // Act - capture.MarkScenarioStart (); - logger.LogInformation ("After mark"); - - // Assert - string scenarioLogs = capture.GetScenarioLogs (); - string allLogs = capture.GetAllLogs (); - - Assert.DoesNotContain ("Before mark", scenarioLogs); - Assert.Contains ("After mark", scenarioLogs); - Assert.Contains ("Before mark", allLogs); - Assert.Contains ("After mark", allLogs); - } - - [Fact] - public void GetScenarioLogs_ReturnsEmptyWhenNoLogsSinceMark () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - logger.LogInformation ("Before mark"); - capture.MarkScenarioStart (); - - // Act - string scenarioLogs = capture.GetScenarioLogs (); - - // Assert - Assert.Equal (string.Empty, scenarioLogs); - } - - [Fact] - public void Clear_ResetsAllState () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - logger.LogError ("Error message"); - capture.MarkScenarioStart (); - logger.LogInformation ("After mark"); - - // Act - capture.Clear (); - - // Assert - Assert.False (capture.HasErrors); - Assert.Equal (0, capture.ScenarioStartPosition); - Assert.Equal (string.Empty, capture.GetScenarioLogs ()); - Assert.Equal (string.Empty, capture.GetAllLogs ()); - } - - [Fact] - public void CreateLogger_ReturnsWorkingLogger () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("TestCategory"); - - // Act - logger.LogInformation ("Info from ILogger"); - logger.LogError ("Error from ILogger"); - - // Assert - string logs = capture.GetAllLogs (); - Assert.Contains ("Info from ILogger", logs); - Assert.Contains ("Error from ILogger", logs); - Assert.True (capture.HasErrors); - } - - [Fact] - public void CreateLogger_IsEnabledReturnsTrue () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("TestCategory"); - - // Assert - Assert.True (logger.IsEnabled (LogLevel.Trace)); - Assert.True (logger.IsEnabled (LogLevel.Debug)); - Assert.True (logger.IsEnabled (LogLevel.Information)); - Assert.True (logger.IsEnabled (LogLevel.Warning)); - Assert.True (logger.IsEnabled (LogLevel.Error)); - Assert.True (logger.IsEnabled (LogLevel.Critical)); - } - - [Fact] - public void CreateLogger_BeginScopeReturnsNull () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("TestCategory"); - - // Act - IDisposable? scope = logger.BeginScope ("test scope"); - - // Assert - Assert.Null (scope); - } - - [Fact] - public void MultipleScenarios_EachGetsSeparateLogs () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - - // Scenario 1 - capture.MarkScenarioStart (); - logger.LogInformation ("Scenario 1 log"); - string scenario1Logs = capture.GetScenarioLogs (); - - // Scenario 2 - capture.MarkScenarioStart (); - logger.LogInformation ("Scenario 2 log"); - string scenario2Logs = capture.GetScenarioLogs (); - - // Assert - Assert.Contains ("Scenario 1 log", scenario1Logs); - Assert.DoesNotContain ("Scenario 2 log", scenario1Logs); - - Assert.Contains ("Scenario 2 log", scenario2Logs); - Assert.DoesNotContain ("Scenario 1 log", scenario2Logs); - - // All logs should contain both - string allLogs = capture.GetAllLogs (); - Assert.Contains ("Scenario 1 log", allLogs); - Assert.Contains ("Scenario 2 log", allLogs); - } - - [Fact] - public void HasErrors_ResetsPerScenario () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - - // Scenario 1 - has errors - capture.MarkScenarioStart (); - logger.LogError ("Error in scenario 1"); - Assert.True (capture.HasErrors); - - // Scenario 2 - no errors - capture.MarkScenarioStart (); - logger.LogInformation ("Info in scenario 2"); - - // Assert - HasErrors should be false for scenario 2 - Assert.False (capture.HasErrors); - } - - [Fact] - public void Dispose_DoesNotThrow () - { - // Arrange - ScenarioLogCapture capture = new (); - ILogger logger = capture.CreateLogger ("Test"); - logger.LogInformation ("Some log"); - - // Act & Assert - should not throw - Exception? ex = Record.Exception (() => capture.Dispose ()); - Assert.Null (ex); - } - - [Fact] - public async Task ThreadSafety_ConcurrentLogging () - { - // Arrange - ScenarioLogCapture capture = new (); - var threadCount = 10; - var logsPerThread = 100; - List tasks = []; - - // Act - log from multiple threads concurrently - for (var t = 0; t < threadCount; t++) - { - int threadId = t; - - tasks.Add (Task.Run (() => - { - ILogger logger = capture.CreateLogger ($"Thread{threadId}"); - - for (var i = 0; i < logsPerThread; i++) - { - logger.LogInformation ($"Thread {threadId} Log {i}"); - } - }, - TestContext.Current.CancellationToken)); - } - - await Task.WhenAll (tasks); - - // Assert - all logs should be captured (check by counting lines) - string allLogs = capture.GetAllLogs (); - int lineCount = allLogs.Split ('\n', StringSplitOptions.RemoveEmptyEntries).Length; - Assert.Equal (threadCount * logsPerThread, lineCount); - } -} diff --git a/Tests/UnitTests/UICatalog/ScenarioTests.cs b/Tests/UnitTests/UICatalog/ScenarioTests.cs deleted file mode 100644 index 93b1765526..0000000000 --- a/Tests/UnitTests/UICatalog/ScenarioTests.cs +++ /dev/null @@ -1,696 +0,0 @@ -#nullable enable -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; -using UICatalog; -using Timeout = System.Threading.Timeout; - -namespace UnitTests.UICatalog; - -public class ScenarioTests : TestsAllViews -{ - public ScenarioTests (ITestOutputHelper output) - { -#if DEBUG_IDISPOSABLE - View.EnableDebugIDisposableAsserts = true; - View.Instances.Clear (); -#endif - _output = output; - } - - private readonly ITestOutputHelper _output; - - /// - /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run. - /// Should find any Scenarios which crash on load or do not respond to . - /// - [Theory] - [MemberData (nameof (AllScenarioTypes))] - public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType) - { - // Disable on Mac due to random failures related to timing issues - if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) - { - _output.WriteLine ($"Skipping Scenario '{scenarioType}' on macOS due to random timeout failures."); - - return; - } - - // Force a complete reset - use ResetModelUsageTracking to allow both models in tests - ApplicationImpl.ResetModelUsageTracking (); - CM.Disable (true); - - _output.WriteLine ($"Running Scenario '{scenarioType}'"); - Scenario? scenario = null; - var scenarioName = string.Empty; - - // Do not use Application.AddTimer for out-of-band watchdogs as - // they will be stopped by Shutdown/ResetState. - Timer? watchdogTimer = null; - var timeoutFired = false; - - // Increase timeout for macOS - it's consistently slower - uint abortTime = 5000; - - if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) - { - abortTime = 10000; - } - - var initialized = false; - var shutdownGracefully = false; - var iterationCount = 0; - Key quitKey = Application.GetDefaultKey (Command.Quit); - - // Track the current application instance for the modern model - IApplication? currentApp = null; - - // Track if we've already unsubscribed to prevent double-removal - var iterationHandlerRemoved = false; - - Exception? scenarioException = null; - - try - { - scenario = Activator.CreateInstance (scenarioType) as Scenario; - scenarioName = scenario!.GetName (); - - // Use thread-local events for modern instance-based model - Application.InstanceInitialized += OnInstanceInitialized; - Application.InstanceDisposed += OnInstanceDisposed; - - Application.ForceDriver = DriverRegistry.Names.ANSI; - scenario!.Main (); - Application.ForceDriver = string.Empty; - } - catch (Exception ex) - { - // Catch exceptions to prevent test host crashes - scenarioException = ex; - _output.WriteLine ($"Scenario '{scenarioName}' threw exception: {ex}"); - } - finally - { - // Ensure cleanup happens regardless of how we exit - Application.InstanceInitialized -= OnInstanceInitialized; - Application.InstanceDisposed -= OnInstanceDisposed; - - // Remove iteration handler if it wasn't removed - if (!iterationHandlerRemoved && currentApp is { }) - { - currentApp.Iteration -= OnApplicationOnIteration; - iterationHandlerRemoved = true; - } - - watchdogTimer?.Dispose (); - - scenario?.Dispose (); - scenario = null; - - ConfigurationManager.Disable (true); - } - - Assert.True (initialized, $"Scenario '{scenarioName}' failed to initialize."); - - if (timeoutFired) - { - _output.WriteLine ($"WARNING: Scenario '{scenarioName}' timed out after {abortTime}ms. This may indicate a performance issue on this runner."); - } - - Assert.True (shutdownGracefully, - $"Scenario '{scenarioName}' failed to quit with {quitKey} after {abortTime}ms and {iterationCount} iterations. " - + $"TimeoutFired={timeoutFired}"); - - // Fail the test if an exception was thrown (but don't crash the test host) - Assert.Null (scenarioException); - -#if DEBUG_IDISPOSABLE - if (View.Instances.Count > 0) - { - foreach (View inst in View.Instances) - { - _output.WriteLine ($"Not Disposed: {inst.ToDebugString ()}"); - } - Assert.Fail ("Views were not disposed properly."); - } -#endif - - return; - - void OnInstanceInitialized (object? s, EventArgs a) - { - currentApp = a.Value; - currentApp.Iteration += OnApplicationOnIteration; - initialized = true; - - // Use a System.Threading.Timer for the watchdog to ensure it's not affected by Application.StopAllTimers - watchdogTimer = new Timer (_ => ForceCloseCallback (), null, (int)abortTime, Timeout.Infinite); - - _output.WriteLine ($"Initialized; shutdownGracefully == {shutdownGracefully}."); - } - - void OnInstanceDisposed (object? s, EventArgs a) - { - // Unsubscribe from Iteration before shutdown assertions - if (!iterationHandlerRemoved && currentApp is { }) - { - currentApp.Iteration -= OnApplicationOnIteration; - iterationHandlerRemoved = true; - } - - shutdownGracefully = true; - _output.WriteLine ($"Disposed; shutdownGracefully == {shutdownGracefully}."); - } - - // If the scenario doesn't close within abortTime ms, this will force it to quit - void ForceCloseCallback () - { - timeoutFired = true; - - _output.WriteLine ($"TIMEOUT FIRED for {scenarioName} after {abortTime}ms. Attempting graceful shutdown."); - - // Don't call ResetState here - let the finally block handle cleanup - // Just try to stop the application gracefully - try - { - if (currentApp?.Initialized == true) - { - currentApp.RequestStop (); - } - } - catch (Exception ex) - { - _output.WriteLine ($"Exception during timeout callback: {ex.Message}"); - } - } - - void OnApplicationOnIteration (object? s, EventArgs a) - { - iterationCount++; - - if (currentApp?.Initialized == true) - { - // Press QuitKey - quitKey = Application.GetDefaultKey (Command.Quit); - _output.WriteLine ($"Attempting to quit with {quitKey} after {iterationCount} iterations."); - - try - { - currentApp.Keyboard.RaiseKeyDownEvent (quitKey); - } - catch (Exception ex) - { - _output.WriteLine ($"Exception raising quit key: {ex.Message}"); - } - - currentApp.Iteration -= OnApplicationOnIteration; - iterationHandlerRemoved = true; - } - } - } - - public static IEnumerable AllScenarioTypes => - typeof (Scenario).Assembly.GetTypes () - .Where (type => type.IsClass && !type.IsAbstract && type.IsSubclassOf (typeof (Scenario))) - .Select (type => new object [] { type }); - - [Fact] - public void Run_All_Views_Tester_Scenario () - { - // Reset model usage tracking to allow legacy static model in this test - ApplicationImpl.ResetModelUsageTracking (); - - // Disable any UIConfig settings - ConfigurationManager.Disable (true); - - View? curView = null; - - // Settings - var xVal = 0; - var yVal = 0; - - var wVal = 0; - var hVal = 0; - List posNames = ["Percent", "AnchorEnd", "Center", "Absolute"]; - List dimNames = ["Auto", "Percent", "Fill", "Absolute"]; - - Application.Init (DriverRegistry.Names.ANSI); - - var top = new Runnable (); - - Dictionary viewClasses = GetAllViewClasses ().ToDictionary (t => t.Name); - - Window leftPane = new () - { - Title = "Classes", - X = 0, - Y = 0, - Width = 15, - Height = Dim.Fill (1), // for status bar - CanFocus = false, - SchemeName = "Runnable" - }; - - ListView classListView = new () - { - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill (), - ShowMarks = false, - SchemeName = "Runnable", - Source = new ListWrapper (new ObservableCollection (viewClasses.Keys.ToList ())) - }; - leftPane.Add (classListView); - - FrameView settingsPane = new () - { - X = Pos.Right (leftPane), - Y = 0, // for menu - Width = Dim.Fill (), - Height = 10, - CanFocus = false, - SchemeName = "Runnable", - Title = "Settings" - }; - - var radioItems = new [] { "Percent(x)", "AnchorEnd(x)", "Center", "Absolute(x)" }; - - FrameView locationFrame = new () - { - X = 0, - Y = 0, - Height = 3 + radioItems.Length, - Width = 36, - Title = "Location (Pos)" - }; - settingsPane.Add (locationFrame); - - var label = new Label { X = 0, Y = 0, Text = "x:" }; - locationFrame.Add (label); - OptionSelector xOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = radioItems }; - TextField xText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{xVal}" }; - locationFrame.Add (xText); - - locationFrame.Add (xOptionSelector); - - radioItems = new [] { "Percent(y)", "AnchorEnd(y)", "Center", "Absolute(y)" }; - label = new Label { X = Pos.Right (xOptionSelector) + 1, Y = 0, Text = "y:" }; - locationFrame.Add (label); - TextField yText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{yVal}" }; - locationFrame.Add (yText); - OptionSelector yOptionSelector = new () { X = Pos.X (label), Y = Pos.Bottom (label), Labels = radioItems }; - locationFrame.Add (yOptionSelector); - - FrameView sizeFrame = new () - { - X = Pos.Right (locationFrame), - Y = Pos.Y (locationFrame), - Height = 3 + radioItems.Length, - Width = 40, - Title = "Size (Dim)" - }; - - radioItems = new [] { "Auto()", "Percent(width)", "Fill(width)", "Absolute(width)" }; - label = new Label { X = 0, Y = 0, Text = "width:" }; - sizeFrame.Add (label); - OptionSelector wOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = radioItems }; - TextField wText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{wVal}" }; - sizeFrame.Add (wText); - sizeFrame.Add (wOptionSelector); - - radioItems = new [] { "Auto()", "Percent(height)", "Fill(height)", "Absolute(height)" }; - label = new Label { X = Pos.Right (wOptionSelector) + 1, Y = 0, Text = "height:" }; - sizeFrame.Add (label); - TextField hText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{hVal}" }; - sizeFrame.Add (hText); - - OptionSelector hOptionSelector = new () { X = Pos.X (label), Y = Pos.Bottom (label), Labels = radioItems }; - sizeFrame.Add (hOptionSelector); - - settingsPane.Add (sizeFrame); - - FrameView hostPane = new () - { - X = Pos.Right (leftPane), - Y = Pos.Bottom (settingsPane), - Width = Dim.Fill (), - Height = Dim.Fill (1), // + 1 for status bar - SchemeName = "Dialog" - }; - - classListView.Accepting += (s, a) => - { - settingsPane.SetFocus (); - a.Handled = true; - }; - - classListView.ValueChanged += (_, _) => - { - // Remove existing class, if any - if (curView is { }) - { - curView.SubViewsLaidOut -= LayoutCompleteHandler; - hostPane.Remove (curView); - curView.Dispose (); - curView = null; - hostPane.FillRect (hostPane.Viewport); - } - - curView = CreateClass (viewClasses.Values.ToArray () [classListView.SelectedItem!.Value]); - }; - - xOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView); - - xText.TextChanged += (s, args) => - { - try - { - xVal = int.Parse (xText.Text); - DimPosChanged (curView); - } - catch - { } - }; - - yText.TextChanged += (s, e) => - { - try - { - yVal = int.Parse (yText.Text); - DimPosChanged (curView); - } - catch - { } - }; - - yOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView); - - wOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView); - - wText.TextChanged += (s, args) => - { - try - { - wVal = int.Parse (wText.Text); - DimPosChanged (curView); - } - catch - { } - }; - - hText.TextChanged += (s, args) => - { - try - { - hVal = int.Parse (hText.Text); - DimPosChanged (curView); - } - catch - { } - }; - - hOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView); - - top.Add (leftPane, settingsPane, hostPane); - - top.LayoutSubViews (); - - curView = CreateClass (viewClasses.First ().Value); - - var iterations = 0; - - Application.Iteration += OnApplicationOnIteration; - Application.Run (top); - Application.Iteration -= OnApplicationOnIteration; - - Assert.Equal (viewClasses.Count, iterations); - - top.Dispose (); - Application.Shutdown (); - ConfigurationManager.Disable (true); - - return; - - void OnApplicationOnIteration (object? s, EventArgs a) - { - iterations++; - - if (iterations < viewClasses.Count) - { - classListView.MoveDown (); - - if (curView is { }) - { - Assert.Equal (curView.GetType ().Name, viewClasses.Values.ToArray () [classListView.SelectedItem!.Value].Name); - } - } - else - { - a.Value?.RequestStop (); - } - } - - void DimPosChanged (View? view) - { - if (view == null) - { - return; - } - - try - { - switch (xOptionSelector.Value) - { - case 0: - view.X = Pos.Percent (xVal); - - break; - - case 1: - view.X = Pos.AnchorEnd (xVal); - - break; - - case 2: - view.X = Pos.Center (); - - break; - - case 3: - view.X = Pos.Absolute (xVal); - - break; - } - - switch (yOptionSelector.Value) - { - case 0: - view.Y = Pos.Percent (yVal); - - break; - - case 1: - view.Y = Pos.AnchorEnd (yVal); - - break; - - case 2: - view.Y = Pos.Center (); - - break; - - case 3: - view.Y = Pos.Absolute (yVal); - - break; - } - - switch (wOptionSelector.Value) - { - case 0: - view.Width = Dim.Percent (wVal); - - break; - - case 1: - view.Width = Dim.Fill (wVal); - - break; - - case 2: - view.Width = Dim.Absolute (wVal); - - break; - } - - switch (hOptionSelector.Value) - { - case 0: - view.Height = Dim.Percent (hVal); - - break; - - case 1: - view.Height = Dim.Fill (hVal); - - break; - - case 2: - view.Height = Dim.Absolute (hVal); - - break; - } - } - catch (Exception e) - { - MessageBox.ErrorQuery (ApplicationImpl.Instance, "Exception", e.Message, "Ok"); - } - - UpdateTitle (view); - } - - void UpdateSettings (View view) - { - var x = view.X.ToString (); - var y = view.Y.ToString (); - - try - { - xOptionSelector.Value = posNames.IndexOf (posNames.First (s => x.Contains (s))); - yOptionSelector.Value = posNames.IndexOf (posNames.First (s => y.Contains (s))); - } - catch (InvalidOperationException e) - { - // This is a hack to work around the fact that the Pos enum doesn't have an "Align" value yet - Debug.WriteLine ($"{e}"); - } - - xText.Text = $"{view.Frame.X}"; - yText.Text = $"{view.Frame.Y}"; - - var w = view.Width!.ToString (); - var h = view.Height!.ToString (); - - wOptionSelector.Value = dimNames.IndexOf (dimNames.First (s => w.Contains (s))); - hOptionSelector.Value = dimNames.IndexOf (dimNames.First (s => h.Contains (s))); - - wText.Text = $"{view.Frame.Width}"; - hText.Text = $"{view.Frame.Height}"; - } - - void UpdateTitle (View? view) => hostPane.Title = $"{view!.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}"; - - View? CreateClass (Type type) - { - // If we are to create a generic Type - if (type.IsGenericType) - { - // For each of the arguments - List typeArguments = new (); - - // use or the original type if applicable - foreach (Type arg in type.GetGenericArguments ()) - { - if (arg.IsValueType && Nullable.GetUnderlyingType (arg) == null) - { - typeArguments.Add (arg); - } - else - { - typeArguments.Add (typeof (object)); - } - } - - // Ensure the type does not contain any generic parameters - if (type.ContainsGenericParameters) - { - Logging.Warning ($"Cannot create an instance of {type} because it contains generic parameters."); - - //throw new ArgumentException ($"Cannot create an instance of {type} because it contains generic parameters."); - return null; - } - - // And change what type we are instantiating from MyClass to MyClass - type = type.MakeGenericType (typeArguments.ToArray ()); - } - - // Instantiate view - var view = Activator.CreateInstance (type) as View; - - if (view is null) - { - return null; - } - - if (view.Width is not DimAuto) - { - view.Width = Dim.Percent (75); - } - - if (view.Height is not DimAuto) - { - view.Height = Dim.Percent (75); - } - - // Set the colorscheme to make it stand out if is null by default - if (!view.HasScheme) - { - view.SchemeName = "Base"; - } - - // If the view supports a Text property, set it so we have something to look at - if (view.GetType ().GetProperty ("Text") != null) - { - try - { - view.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (view, new [] { "Test Text" }); - } - catch (TargetInvocationException e) - { - MessageBox.ErrorQuery (ApplicationImpl.Instance, "Exception", e.InnerException!.Message, "Ok"); - view = null; - } - } - - // If the view supports a Title property, set it so we have something to look at - if (view != null && view.GetType ().GetProperty ("Title") != null) - { - if (view.GetType ().GetProperty ("Title")!.PropertyType == typeof (string)) - { - view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" }); - } - else - { - view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" }); - } - } - - // If the view supports a Source property, set it so we have something to look at - if (view != null - && view.GetType ().GetProperty ("Source") != null - && view.GetType ().GetProperty ("Source")!.PropertyType == typeof (IListDataSource)) - { - ListWrapper source = new (["Test Text #1", "Test Text #2", "Test Text #3"]); - view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source }); - } - - // Add - hostPane.Add (view); - - //DimPosChanged (); - hostPane.LayoutSubViews (); - hostPane.ClearViewport (); - hostPane.SetNeedsDraw (); - UpdateSettings (view!); - UpdateTitle (view); - - view!.SubViewsLaidOut += LayoutCompleteHandler; - - return view; - } - - void LayoutCompleteHandler (object? sender, LayoutEventArgs args) => UpdateTitle (curView); - } -} diff --git a/Tests/UnitTests/View/Adornment/BorderTests.cs b/Tests/UnitTests/View/Adornment/BorderTests.cs deleted file mode 100644 index 83cae48975..0000000000 --- a/Tests/UnitTests/View/Adornment/BorderTests.cs +++ /dev/null @@ -1,943 +0,0 @@ -namespace UnitTests.ViewBaseTests; - -public class BorderTests (ITestOutputHelper output) -{ - [Fact] - [SetupFakeApplication] - public void Border_Parent_HasFocus_Title_Uses_FocusAttribute () - { - var superView = new View { Driver = ApplicationImpl.Instance.Driver, Width = 10, Height = 10, CanFocus = true }; - var otherView = new View { Width = 0, Height = 0, CanFocus = true }; - superView.Add (otherView); - - var view = new View { Title = "A", Height = 2, Width = 5 }; - superView.Add (view); - - view.Border.Thickness = new Thickness (0, 1, 0, 0); - view.Border.LineStyle = LineStyle.Single; - - view.SetScheme (new Scheme { Normal = new Attribute (Color.Red, Color.Green), Focus = new Attribute (Color.Green, Color.Red) }); - Assert.NotEqual (view.GetScheme ().Normal.Foreground, view.GetScheme ().Focus.Foreground); - Assert.Equal (ColorName16.Red, view.Border.View!.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); - Assert.Equal (ColorName16.Green, view.Border.View!.GetAttributeForRole (VisualRole.Focus).Foreground.GetClosestNamedColor16 ()); - Assert.Equal (view.GetAttributeForRole (VisualRole.Focus), view.Border.View!.GetAttributeForRole (VisualRole.Focus)); - - superView.BeginInit (); - superView.EndInit (); - superView.Draw (); - - var expected = @"─┤A├─"; - DriverAssert.AssertDriverContentsAre (expected, output); - DriverAssert.AssertDriverAttributesAre ("00000", output, null, view.GetScheme ().Normal); - - view.CanFocus = true; - view.SetFocus (); - view.SetClipToScreen (); - view.Draw (); - Assert.Equal (view.GetAttributeForRole (VisualRole.Focus), view.Border.View!.GetAttributeForRole (VisualRole.Focus)); - Assert.Equal (view.GetScheme ().Focus.Foreground, view.Border.View!.GetAttributeForRole (VisualRole.Focus).Foreground); - Assert.Equal (view.GetScheme ().Normal.Foreground, view.Border.View!.GetAttributeForRole (VisualRole.Normal).Foreground); - DriverAssert.AssertDriverAttributesAre ("00100", output, null, view.GetScheme ().Normal, view.GetAttributeForRole (VisualRole.Focus)); - } - - [Fact] - [SetupFakeApplication] - public void Border_Uses_Parent_Scheme () - { - var view = new View { Driver = ApplicationImpl.Instance.Driver, Title = "A", Height = 2, Width = 5 }; - view.Border.Thickness = new Thickness (0, 1, 0, 0); - view.Border.LineStyle = LineStyle.Single; - - view.SetScheme (new Scheme { Normal = new Attribute (Color.Red, Color.Green), Focus = new Attribute (Color.Green, Color.Red) }); - Assert.Equal (ColorName16.Red, view.Border.View!.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); - Assert.Equal (ColorName16.Green, view.Border.View!.GetAttributeForRole (VisualRole.Focus).Foreground.GetClosestNamedColor16 ()); - Assert.Equal (view.GetAttributeForRole (VisualRole.Normal), view.Border.View!.GetAttributeForRole (VisualRole.Normal)); - Assert.Equal (view.GetAttributeForRole (VisualRole.Focus), view.Border.View!.GetAttributeForRole (VisualRole.Focus)); - - view.BeginInit (); - view.EndInit (); - view.Draw (); - - var expected = @"─┤A├─"; - DriverAssert.AssertDriverContentsAre (expected, output); - DriverAssert.AssertDriverAttributesAre ("00000", output, null, view.GetScheme ().Normal); - } - - [Theory] - [AutoInitShutdown] - [InlineData (0)] - [InlineData (1)] - [InlineData (2)] - [InlineData (3)] - [InlineData (4)] - [InlineData (5)] - [InlineData (6)] - [InlineData (7)] - [InlineData (8)] - [InlineData (9)] - [InlineData (10)] - public void Border_With_Title_Border_Double_Thickness_Top_Four_Size_Width (int width) - { - var win = new Window { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness = win.Border.Thickness with { Top = 4 }; - - SessionToken rs = Application.Begin (win); - - Application.Driver!.SetScreenSize (width, 5); - AutoInitShutdownAttribute.RunIteration (); - var expected = string.Empty; - - switch (width) - { - case 1: - Assert.Equal (new Rectangle (0, 0, 1, 5), win.Frame); - - expected = @" -║ -║ -║"; - - break; - - case 2: - Assert.Equal (new Rectangle (0, 0, 2, 5), win.Frame); - - expected = @" -╔╗ -║║ -╚╝"; - - break; - - case 3: - Assert.Equal (new Rectangle (0, 0, 3, 5), win.Frame); - - expected = @" -╔═╗ -║ ║ -╚═╝"; - - break; - - case 4: - Assert.Equal (new Rectangle (0, 0, 4, 5), win.Frame); - - expected = @" - ╒╕ -╔╡╞╗ -║╘╛║ -╚══╝"; - - break; - - case 5: - Assert.Equal (new Rectangle (0, 0, 5, 5), win.Frame); - - expected = @" - ╒═╕ -╔╡1╞╗ -║╘═╛║ -╚═══╝"; - - break; - - case 6: - Assert.Equal (new Rectangle (0, 0, 6, 5), win.Frame); - - expected = @" - ╒══╕ -╔╡12╞╗ -║╘══╛║ -╚════╝"; - - break; - - case 7: - Assert.Equal (new Rectangle (0, 0, 7, 5), win.Frame); - - expected = @" - ╒═══╕ -╔╡123╞╗ -║╘═══╛║ -╚═════╝"; - - break; - - case 8: - Assert.Equal (new Rectangle (0, 0, 8, 5), win.Frame); - - expected = @" - ╒════╕ -╔╡1234╞╗ -║╘════╛║ -╚══════╝"; - - break; - - case 9: - Assert.Equal (new Rectangle (0, 0, 9, 5), win.Frame); - - expected = @" - ╒════╕ -╔╡1234╞═╗ -║╘════╛ ║ -╚═══════╝"; - - break; - - case 10: - Assert.Equal (new Rectangle (0, 0, 10, 5), win.Frame); - - expected = @" - ╒════╕ -╔╡1234╞══╗ -║╘════╛ ║ -╚════════╝"; - - break; - } - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - win.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (0)] - [InlineData (1)] - [InlineData (2)] - [InlineData (3)] - [InlineData (4)] - [InlineData (5)] - [InlineData (6)] - [InlineData (7)] - [InlineData (8)] - [InlineData (9)] - [InlineData (10)] - public void Border_With_Title_Border_Double_Thickness_Top_Three_Size_Width (int width) - { - Window win = new () { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness = win.Border.Thickness with { Top = 3 }; - - SessionToken rs = Application.Begin (win); - - Application.Driver!.SetScreenSize (width, 4); - AutoInitShutdownAttribute.RunIteration (); - var expected = string.Empty; - - switch (width) - { - case 1: - Assert.Equal (new Rectangle (0, 0, 1, 4), win.Frame); - - expected = @" -║ -║ -║"; - - break; - - case 2: - Assert.Equal (new Rectangle (0, 0, 2, 4), win.Frame); - - expected = @" -╔╗ -║║ -╚╝"; - - break; - - case 3: - Assert.Equal (new Rectangle (0, 0, 3, 4), win.Frame); - - expected = @" -╔═╗ -║ ║ -╚═╝"; - - break; - - case 4: - Assert.Equal (new Rectangle (0, 0, 4, 4), win.Frame); - - expected = @" - ╒╕ -╔╡╞╗ -║╘╛║ -╚══╝"; - - break; - - case 5: - Assert.Equal (new Rectangle (0, 0, 5, 4), win.Frame); - - expected = @" - ╒═╕ -╔╡1╞╗ -║╘═╛║ -╚═══╝"; - - break; - - case 6: - Assert.Equal (new Rectangle (0, 0, 6, 4), win.Frame); - - expected = @" - ╒══╕ -╔╡12╞╗ -║╘══╛║ -╚════╝"; - - break; - - case 7: - Assert.Equal (new Rectangle (0, 0, 7, 4), win.Frame); - - expected = @" - ╒═══╕ -╔╡123╞╗ -║╘═══╛║ -╚═════╝"; - - break; - - case 8: - Assert.Equal (new Rectangle (0, 0, 8, 4), win.Frame); - - expected = @" - ╒════╕ -╔╡1234╞╗ -║╘════╛║ -╚══════╝"; - - break; - - case 9: - Assert.Equal (new Rectangle (0, 0, 9, 4), win.Frame); - - expected = @" - ╒════╕ -╔╡1234╞═╗ -║╘════╛ ║ -╚═══════╝"; - - break; - - case 10: - Assert.Equal (new Rectangle (0, 0, 10, 4), win.Frame); - - expected = @" - ╒════╕ -╔╡1234╞══╗ -║╘════╛ ║ -╚════════╝"; - - break; - } - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - win.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (0)] - [InlineData (1)] - [InlineData (2)] - [InlineData (3)] - [InlineData (4)] - [InlineData (5)] - [InlineData (6)] - [InlineData (7)] - [InlineData (8)] - [InlineData (9)] - [InlineData (10)] - public void Border_With_Title_Border_Double_Thickness_Top_Two_Size_Width (int width) - { - var win = new Window { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness = win.Border.Thickness with { Top = 2 }; - - SessionToken rs = Application.Begin (win); - - Application.Driver!.SetScreenSize (width, 4); - AutoInitShutdownAttribute.RunIteration (); - var expected = string.Empty; - - switch (width) - { - case 1: - Assert.Equal (new Rectangle (0, 0, 1, 4), win.Frame); - - expected = @" -║ -║ -║"; - - break; - - case 2: - Assert.Equal (new Rectangle (0, 0, 2, 4), win.Frame); - - expected = @" -╔╗ -║║ -╚╝"; - - break; - - case 3: - Assert.Equal (new Rectangle (0, 0, 3, 4), win.Frame); - - expected = @" -╔═╗ -║ ║ -╚═╝"; - - break; - - case 4: - Assert.Equal (new Rectangle (0, 0, 4, 4), win.Frame); - - expected = @" - ╒╕ -╔╛╘╗ -║ ║ -╚══╝"; - - break; - - case 5: - Assert.Equal (new Rectangle (0, 0, 5, 4), win.Frame); - - expected = @" - ╒═╕ -╔╛1╘╗ -║ ║ -╚═══╝"; - - break; - - case 6: - Assert.Equal (new Rectangle (0, 0, 6, 4), win.Frame); - - expected = @" - ╒══╕ -╔╛12╘╗ -║ ║ -╚════╝"; - - break; - - case 7: - Assert.Equal (new Rectangle (0, 0, 7, 4), win.Frame); - - expected = @" - ╒═══╕ -╔╛123╘╗ -║ ║ -╚═════╝"; - - break; - - case 8: - Assert.Equal (new Rectangle (0, 0, 8, 4), win.Frame); - - expected = @" - ╒════╕ -╔╛1234╘╗ -║ ║ -╚══════╝"; - - break; - - case 9: - Assert.Equal (new Rectangle (0, 0, 9, 4), win.Frame); - - expected = @" - ╒════╕ -╔╛1234╘═╗ -║ ║ -╚═══════╝"; - - break; - - case 10: - Assert.Equal (new Rectangle (0, 0, 10, 4), win.Frame); - - expected = @" - ╒════╕ -╔╛1234╘══╗ -║ ║ -╚════════╝"; - - break; - } - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - win.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (0)] - [InlineData (1)] - [InlineData (2)] - [InlineData (3)] - public void Border_With_Title_Size_Height (int height) - { - var win = new Window { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill () }; - - SessionToken rs = Application.Begin (win); - - Application.Driver!.SetScreenSize (20, height); - AutoInitShutdownAttribute.RunIteration (); - var expected = string.Empty; - - switch (height) - { - case 0: - //Assert.Equal (new (0, 0, 17, 0), subview.Frame); - expected = @" -"; - - break; - - case 1: - //Assert.Equal (new (0, 0, 17, 0), subview.Frame); - expected = @" -─┤1234├─────────────"; - - break; - - case 2: - //Assert.Equal (new (0, 0, 17, 1), subview.Frame); - expected = @" -┌┤1234├────────────┐ -└──────────────────┘ -"; - - break; - - case 3: - //Assert.Equal (new (0, 0, 17, 2), subview.Frame); - expected = @" -┌┤1234├────────────┐ -│ │ -└──────────────────┘ -"; - - break; - } - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - win.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (0)] - [InlineData (1)] - [InlineData (2)] - [InlineData (3)] - [InlineData (4)] - [InlineData (5)] - [InlineData (6)] - [InlineData (7)] - [InlineData (8)] - [InlineData (9)] - [InlineData (10)] - public void Border_With_Title_Size_Width (int width) - { - var win = new Window { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill () }; - - SessionToken rs = Application.Begin (win); - - Application.Driver!.SetScreenSize (width, 3); - AutoInitShutdownAttribute.RunIteration (); - var expected = string.Empty; - - switch (width) - { - case 1: - //Assert.Equal (new (0, 0, 17, 0), subview.Frame); - expected = @" -│ -│ -│"; - - break; - - case 2: - //Assert.Equal (new (0, 0, 17, 1), subview.Frame); - expected = @" -┌┐ -││ -└┘"; - - break; - - case 3: - //Assert.Equal (new (0, 0, 17, 2), subview.Frame); - expected = @" -┌─┐ -│ │ -└─┘ -"; - - break; - - case 4: - //Assert.Equal (new (0, 0, 17, 3), subview.Frame); - expected = @" -┌┤├┐ -│ │ -└──┘"; - - break; - - case 5: - //Assert.Equal (new (0, 0, 17, 3), subview.Frame); - expected = @" -┌┤1├┐ -│ │ -└───┘"; - - break; - - case 6: - //Assert.Equal (new (0, 0, 17, 3), subview.Frame); - expected = @" -┌┤12├┐ -│ │ -└────┘"; - - break; - - case 7: - //Assert.Equal (new (0, 0, 17, 3), subview.Frame); - expected = @" -┌┤123├┐ -│ │ -└─────┘"; - - break; - - case 8: - //Assert.Equal (new (0, 0, 17, 3), subview.Frame); - expected = @" -┌┤1234├┐ -│ │ -└──────┘"; - - break; - - case 9: - //Assert.Equal (new (0, 0, 17, 3), subview.Frame); - expected = @" -┌┤1234├─┐ -│ │ -└───────┘"; - - break; - - case 10: - //Assert.Equal (new (0, 0, 17, 3), subview.Frame); - expected = @" -┌┤1234├──┐ -│ │ -└────────┘"; - - break; - } - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - win.Dispose (); - } - - [Theory] - [InlineData (0, 0, 0, 2, 2)] - [InlineData (1, 0, 0, 4, 4)] - [InlineData (2, 0, 0, 6, 6)] - [InlineData (1, 1, 0, 5, 4)] - [InlineData (1, 0, 1, 4, 5)] - [InlineData (1, 1, 1, 5, 5)] - [InlineData (1, 10, 10, 14, 14)] - public void FrameToScreen_NestedSuperView_WithBorder (int superOffset, int frameX, int frameY, int expectedScreenX, int expectedScreenY) - { - var superSuper = new View - { - X = superOffset, - Y = superOffset, - Width = 30, - Height = 30, - BorderStyle = LineStyle.Single - }; - - var super = new View - { - X = superOffset, - Y = superOffset, - Width = 20, - Height = 20, - BorderStyle = LineStyle.Single - }; - superSuper.Add (super); - - var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 }; - super.Add (view); - superSuper.Layout (); - - var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10); - Rectangle actual = view.FrameToScreen (); - Assert.Equal (expected, actual); - } - - [Theory] - [InlineData (0, 0, 0, 1, 1)] - [InlineData (1, 0, 0, 2, 2)] - [InlineData (2, 0, 0, 3, 3)] - [InlineData (1, 1, 0, 3, 2)] - [InlineData (1, 0, 1, 2, 3)] - [InlineData (1, 1, 1, 3, 3)] - [InlineData (1, 10, 10, 12, 12)] - public void FrameToScreen_SuperView_WithBorder (int superOffset, int frameX, int frameY, int expectedScreenX, int expectedScreenY) - { - var super = new View - { - X = superOffset, - Y = superOffset, - Width = 20, - Height = 20, - BorderStyle = LineStyle.Single - }; - - var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 }; - super.Add (view); - super.Layout (); - - var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10); - Rectangle actual = view.FrameToScreen (); - Assert.Equal (expected, actual); - } - - [Fact] - [AutoInitShutdown] - public void HasSuperView () - { - var top = new Runnable (); - top.BorderStyle = LineStyle.Double; - - var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single }; - - top.Add (frame); - SessionToken rs = Application.Begin (top); - - Application.Driver!.SetScreenSize (5, 5); - AutoInitShutdownAttribute.RunIteration (); - - var expected = @" -╔═══╗ -║┌─┐║ -║│ │║ -║└─┘║ -╚═══╝"; - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void HasSuperView_Title () - { - var top = new Runnable (); - top.BorderStyle = LineStyle.Double; - - var frame = new FrameView { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single }; - - top.Add (frame); - SessionToken rs = Application.Begin (top); - - Application.Driver!.SetScreenSize (10, 4); - AutoInitShutdownAttribute.RunIteration (); - - var expected = @" -╔════════╗ -║┌┤1234├┐║ -║└──────┘║ -╚════════╝"; - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void NoSuperView () - { - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - - SessionToken rs = Application.Begin (win); - - Application.Driver!.SetScreenSize (3, 3); - AutoInitShutdownAttribute.RunIteration (); - - var expected = @" -┌─┐ -│ │ -└─┘"; - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - win.Dispose (); - } - - [Fact] - public void View_SetBorderStyle () - { - var view = new View (); - view.BorderStyle = LineStyle.Single; - Assert.Equal (LineStyle.Single, view.BorderStyle); - Assert.Equal (new Thickness (1), view.Border.Thickness); - - view.BorderStyle = LineStyle.Double; - Assert.Equal (LineStyle.Double, view.BorderStyle); - Assert.Equal (new Thickness (1), view.Border.Thickness); - - view.BorderStyle = LineStyle.None; - Assert.Equal (LineStyle.None, view.BorderStyle); - Assert.Equal (Thickness.Empty, view.Border.Thickness); - view.Dispose (); - } - - [Theory] - [InlineData (false, - @" -┌───┐ -│ ║ │ -│═┌┄│ -│ ┊ │ -└───┘")] - [InlineData (true, - @" -╔═╦─┐ -║ ║ │ -╠═╬┄┤ -│ ┊ ┊ -└─┴┄┘")] - [SetupFakeApplication] - public void SuperViewRendersLineCanvas_No_SubViews_AutoJoinsLines (bool superViewRendersLineCanvas, string expected) - { - var superView = new View - { - Driver = ApplicationImpl.Instance.Driver, - Id = "superView", - Width = 5, - Height = 5, - BorderStyle = LineStyle.Single - }; - - var view1 = new View - { - Id = "view1", - Width = 3, - Height = 3, - X = -1, - Y = -1, - BorderStyle = LineStyle.Double, - SuperViewRendersLineCanvas = superViewRendersLineCanvas - }; - - var view2 = new View - { - Id = "view2", - Width = 3, - Height = 3, - X = 1, - Y = 1, - BorderStyle = LineStyle.Dotted, - SuperViewRendersLineCanvas = superViewRendersLineCanvas - }; - - superView.Add (view1, view2); - - superView.BeginInit (); - superView.EndInit (); - superView.Draw (); - - DriverAssert.AssertDriverContentsAre (expected, output); - } - - [Theory] - [InlineData (false, - @" -┌┤A├──────┐ -│ ║ │ -│ ║ │ -│════┌┤C├┄│ -│ ┊ │ -│ ┊ │ -└─────────┘")] - [InlineData (true, - @" -╔╡A╞═╦────┐ -║ ║ │ -║ ║ │ -╠════╬┤C├┄┤ -│ ┊ ┊ -│ ┊ ┊ -└────┴┄┄┄┄┘")] - [SetupFakeApplication] - public void SuperViewRendersLineCanvas_Title_AutoJoinsLines (bool superViewRendersLineCanvas, string expected) - { - var superView = new View - { - Driver = ApplicationImpl.Instance.Driver, - Id = "superView", - Title = "A", - Width = 11, - Height = 7, - CanFocus = true, - BorderStyle = LineStyle.Single - }; - - var view1 = new View - { - Id = "view1", - Title = "B", - Width = 6, - Height = 4, - X = -1, - Y = -1, - CanFocus = true, - BorderStyle = LineStyle.Double, - SuperViewRendersLineCanvas = superViewRendersLineCanvas - }; - - var view2 = new View - { - Id = "view2", - Title = "C", - Width = 6, - Height = 4, - X = 4, - Y = 2, - CanFocus = true, - BorderStyle = LineStyle.Dotted, - SuperViewRendersLineCanvas = superViewRendersLineCanvas - }; - - superView.Add (view1, view2); - - superView.BeginInit (); - superView.EndInit (); - superView.Draw (); - - DriverAssert.AssertDriverContentsAre (expected, output); - } -} diff --git a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs deleted file mode 100644 index a146e95dd3..0000000000 --- a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -namespace UnitTests.ViewBaseTests; - -public class ShadowStyleTests (ITestOutputHelper output) -{ - [Theory] - [InlineData (ShadowStyles.None, - """ - 011 - 111 - 111 - """)] - [InlineData (ShadowStyles.Transparent, - """ - 031 - 131 - 111 - """)] - [InlineData (ShadowStyles.Opaque, - """ - 021 - 221 - 111 - """)] - [SetupFakeApplication] - public void ShadowView_Colors (ShadowStyles style, string expectedAttrs) - { - Application.Driver!.SetScreenSize (5, 5); - Color fg = Color.Red; - Color bg = Color.Green; - - // 0 - View - // 1 - SuperView - // 2 - Opaque - fg is Black, bg is SuperView.Bg - // 3 - Transparent - fg is darker fg, bg is darker bg - Attribute [] attributes = { Attribute.Default, new (fg, bg), new (Color.Black, bg), new (fg.GetDimmerColor (), bg.GetDimmerColor ()) }; - - var superView = new Runnable { Driver = ApplicationImpl.Instance.Driver, Height = 3, Width = 3, Text = "012ABC!@#" }; - superView.SetScheme (new Scheme (new Attribute (fg, bg))); - superView.TextFormatter.WordWrap = true; - - View view = new () { Width = Dim.Auto (), Height = Dim.Auto (), Text = "*", ShadowStyle = style }; - view.SetScheme (new Scheme (Attribute.Default)); - - superView.Add (view); - Application.Begin (superView); - Application.LayoutAndDraw (true); - DriverAssert.AssertDriverAttributesAre (expectedAttrs, output, Application.Driver, attributes); - superView.Dispose (); - Application.ResetState (true); - } - - // Visual tests - [Theory] - [InlineData (ShadowStyles.None, - """ - 01#$ - AB#$ - !@#$ - !@#$ - """)] - [InlineData (ShadowStyles.Opaque, - """ - 01▖$ - AB▌$ - ▝▀▘$ - !@#$ - """)] - [InlineData (ShadowStyles.Transparent, - """ - 01#$ - AB#$ - !@#$ - !@#$ - """)] - [SetupFakeApplication] - public void Visual_Test (ShadowStyles style, string expected) - { - Application.Driver!.SetScreenSize (5, 5); - - var superView = new Runnable { Driver = ApplicationImpl.Instance.Driver, Width = 4, Height = 4, Text = "!@#$".Repeat (4)! }; - superView.TextFormatter.WordWrap = true; - - var view = new View { Text = "01\nAB", Width = Dim.Auto (), Height = Dim.Auto () }; - view.ShadowStyle = style; - superView.Add (view); - Application.Begin (superView); - Application.LayoutAndDraw (true); - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - superView.Dispose (); - Application.ResetState (true); - } - - [Theory] - [InlineData (ShadowStyles.None, 0, 0, 0, 0)] - [InlineData (ShadowStyles.Opaque, 1, 0, 0, 1)] - [InlineData (ShadowStyles.Transparent, 1, 0, 0, 1)] - [AutoInitShutdown] - public void ShadowStyle_LeftButtonPressed_Causes_Movement (ShadowStyles style, int expectedLeft, int expectedTop, int expectedRight, int expectedBottom) - { - var superView = new View { Height = 10, Width = 10, App = ApplicationImpl.Instance }; - - View view = new () - { - Width = Dim.Auto (), - Height = Dim.Auto (), - Text = "0123", - MouseHighlightStates = MouseState.Pressed, - ShadowStyle = style, - CanFocus = true - }; - - superView.Add (view); - superView.BeginInit (); - superView.EndInit (); - - Thickness origThickness = view.Margin.Thickness; - view.NewMouseEvent (new Mouse { Flags = MouseFlags.LeftButtonPressed, Position = new Point (0, 0) }); - Assert.Equal (new Thickness (expectedLeft, expectedTop, expectedRight, expectedBottom), view.Margin.Thickness); - - view.NewMouseEvent (new Mouse { Flags = MouseFlags.LeftButtonReleased, Position = new Point (0, 0) }); - Assert.Equal (origThickness, view.Margin.Thickness); - - // LeftButtonPressed, LeftButtonReleased cause Application.Mouse.IsGrabbed to be set - Application.ResetState (true); - } -} diff --git a/Tests/UnitTests/View/ArrangementTests.cs b/Tests/UnitTests/View/ArrangementTests.cs deleted file mode 100644 index 89d4f2072e..0000000000 --- a/Tests/UnitTests/View/ArrangementTests.cs +++ /dev/null @@ -1,177 +0,0 @@ -namespace UnitTests.ViewBaseTests; - -public class ArrangementTests (ITestOutputHelper output) -{ - private readonly ITestOutputHelper _output = output; - - [Fact] - public void MouseGrabHandler_WorksWithMovableView_UsingNewMouseEvent () - { - // This test proves that MouseGrabHandler works correctly with concurrent unit tests - // using NewMouseEvent directly on views, without requiring Application.Init - - var superView = new View { Width = 80, Height = 25 }; - superView.App = ApplicationImpl.Instance; - - var movableView = new View - { - Arrangement = ViewArrangement.Movable, - BorderStyle = LineStyle.Single, - X = 10, - Y = 10, - Width = 20, - Height = 10 - }; - - superView.Add (movableView); - - // Verify initial state - Assert.NotNull (movableView.Border); - Assert.False (Application.Mouse.IsGrabbed (movableView.Border.View!)); - - // Simulate mouse press on the border to start dragging - var pressEvent = new Mouse - { - Position = new Point (1, 0), // Top border area - Flags = MouseFlags.LeftButtonPressed - }; - - bool? result = movableView.Border.View!.NewMouseEvent (pressEvent); - - // The border should have grabbed the mouse - Assert.True (result); - Assert.True (superView.App.Mouse.IsGrabbed (movableView.Border.View!)); - - // Simulate mouse drag - var dragEvent = new Mouse { Position = new Point (5, 2), Flags = MouseFlags.LeftButtonPressed | MouseFlags.PositionReport }; - - result = movableView.Border.View!.NewMouseEvent (dragEvent); - Assert.True (result); - - // Mouse should still be grabbed - Assert.True (superView.App.Mouse.IsGrabbed (movableView.Border.View!)); - - // Simulate mouse release to end dragging - var releaseEvent = new Mouse { Position = new Point (5, 2), Flags = MouseFlags.LeftButtonReleased }; - - result = movableView.Border.View!.NewMouseEvent (releaseEvent); - Assert.True (result); - - // Mouse should be released - Assert.False (superView.App.Mouse.IsGrabbed (movableView.Border.View!)); - } - - [Fact] - public void MouseGrabHandler_WorksWithResizableView_UsingNewMouseEvent () - { - // This test proves MouseGrabHandler works for resizing operations - - var superView = new View { App = ApplicationImpl.Instance, Width = 80, Height = 25 }; - - var resizableView = new View - { - Arrangement = ViewArrangement.RightResizable, - BorderStyle = LineStyle.Single, - X = 10, - Y = 10, - Width = 20, - Height = 10 - }; - - superView.Add (resizableView); - - // Verify initial state - Assert.NotNull (resizableView.Border); - Assert.False (Application.Mouse.IsGrabbed (resizableView.Border.View!)); - - // Calculate position on right border (border is at right edge) - // Border.Frame.X is relative to parent, so we use coordinates relative to the border - var pressEvent = new Mouse - { - Position = new Point (resizableView.Border.GetFrame ().Width - 1, 5), // Right border area - Flags = MouseFlags.LeftButtonPressed - }; - - bool? result = resizableView.Border.View!.NewMouseEvent (pressEvent); - - // The border should have grabbed the mouse for resizing - Assert.True (result); - Assert.True (superView.App.Mouse.IsGrabbed (resizableView.Border.View!)); - - // Simulate dragging to resize - var dragEvent = new Mouse - { - Position = new Point (resizableView.Border.GetFrame ().Width + 3, 5), Flags = MouseFlags.LeftButtonPressed | MouseFlags.PositionReport - }; - - result = resizableView.Border.View!.NewMouseEvent (dragEvent); - Assert.True (result); - Assert.True (superView.App.Mouse.IsGrabbed (resizableView.Border.View!)); - - // Simulate mouse release - var releaseEvent = new Mouse { Position = new Point (resizableView.Border.GetFrame ().Width + 3, 5), Flags = MouseFlags.LeftButtonReleased }; - - result = resizableView.Border.View!.NewMouseEvent (releaseEvent); - Assert.True (result); - - // Mouse should be released - Assert.False (superView.App.Mouse.IsGrabbed (resizableView.Border.View!)); - } - - [Fact] - public void MouseGrabHandler_ReleasesOnMultipleViews () - { - // This test verifies MouseGrabHandler properly releases when switching between views - - var superView = new View { Width = 80, Height = 25 }; - superView.App = ApplicationImpl.Instance; - - var view1 = new View - { - Arrangement = ViewArrangement.Movable, - BorderStyle = LineStyle.Single, - X = 10, - Y = 10, - Width = 15, - Height = 8 - }; - - var view2 = new View - { - Arrangement = ViewArrangement.Movable, - BorderStyle = LineStyle.Single, - X = 30, - Y = 10, - Width = 15, - Height = 8 - }; - - superView.Add (view1, view2); - superView.BeginInit (); - superView.EndInit (); - - // Grab mouse on first view - var pressEvent1 = new Mouse { Position = new Point (1, 0), Flags = MouseFlags.LeftButtonPressed }; - - view1.Border.View!.NewMouseEvent (pressEvent1); - Assert.True (superView.App.Mouse.IsGrabbed (view1.Border.View!)); - - // Release on first view - var releaseEvent1 = new Mouse { Position = new Point (1, 0), Flags = MouseFlags.LeftButtonReleased }; - - view1.Border.View!.NewMouseEvent (releaseEvent1); - Assert.False (Application.Mouse.IsGrabbed (view1.Border.View!)); - - // Grab mouse on second view - var pressEvent2 = new Mouse { Position = new Point (1, 0), Flags = MouseFlags.LeftButtonPressed }; - - view2.Border.View!.NewMouseEvent (pressEvent2); - Assert.True (superView.App.Mouse.IsGrabbed (view2.Border.View!)); - - // Release on second view - var releaseEvent2 = new Mouse { Position = new Point (1, 0), Flags = MouseFlags.LeftButtonReleased }; - - view2.Border.View!.NewMouseEvent (releaseEvent2); - Assert.False (superView.App.Mouse.IsGrabbed (view2.Border.View!)); - } -} diff --git a/Tests/UnitTests/View/DiagnosticsTests.cs b/Tests/UnitTests/View/DiagnosticsTests.cs deleted file mode 100644 index e56a09fec1..0000000000 --- a/Tests/UnitTests/View/DiagnosticsTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -#nullable enable -namespace UnitTests.ViewBaseTests; - -/// -/// Tests static property and enum. -/// -/// -[Trait ("Category", "Output")] -public class DiagnosticTests () -{ - /// - /// /// Tests static property and enum. - /// /// - /// - [Fact] - public void Diagnostics_Sets () - { - // View.Diagnostics is a static property that returns the current diagnostic flags. - Assert.Equal (ViewDiagnosticFlags.Off, View.Diagnostics); - - // View.Diagnostics can be set to a new value. - View.Diagnostics = ViewDiagnosticFlags.Thickness; - Assert.Equal (ViewDiagnosticFlags.Thickness, View.Diagnostics); - - // Ensure we turn off at the end of the test - View.Diagnostics = ViewDiagnosticFlags.Off; - } -} diff --git a/Tests/UnitTests/View/Draw/ClipTests.cs b/Tests/UnitTests/View/Draw/ClipTests.cs deleted file mode 100644 index d301963bff..0000000000 --- a/Tests/UnitTests/View/Draw/ClipTests.cs +++ /dev/null @@ -1,293 +0,0 @@ -#nullable enable -using System.Text; - -namespace UnitTests.ViewBaseTests; - -[Trait ("Category", "Output")] -public class ClipTests (ITestOutputHelper _output) -{ - [Fact] - [SetupFakeApplication] - public void Move_Is_Not_Constrained_To_Viewport () - { - var view = new View - { - App = ApplicationImpl.Instance, - X = 1, - Y = 1, - Width = 3, - Height = 3 - }; - view.Margin.Thickness = new Thickness (1); - - view.Move (0, 0); - Assert.Equal (new Point (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); - - view.Move (-1, -1); - Assert.Equal (new Point (1, 1), new Point (Application.Driver!.Col, Application.Driver!.Row)); - - view.Move (1, 1); - Assert.Equal (new Point (3, 3), new Point (Application.Driver!.Col, Application.Driver!.Row)); - } - - [Fact] - [SetupFakeApplication] - public void AddRune_Is_Constrained_To_Viewport () - { - var view = new View - { - App = ApplicationImpl.Instance, - X = 1, - Y = 1, - Width = 3, - Height = 3 - }; - view.Padding.GetOrCreateView (); - view.Padding.Thickness = new Thickness (1); - view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness; - view.BeginInit (); - view.EndInit (); - view.Draw (); - - // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen) - Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme); - - // When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped. - view.AddRune (0, 0, Glyphs.WideGlyphReplacement); - Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme); - - view.AddRune (-1, -1, Glyphs.WideGlyphReplacement); - Assert.Equal ("P", Application.Driver?.Contents! [1, 1].Grapheme); - - view.AddRune (1, 1, Glyphs.WideGlyphReplacement); - Assert.Equal ("P", Application.Driver?.Contents! [3, 3].Grapheme); - } - - [Theory] - [InlineData (0, 0, 1, 1)] - [InlineData (0, 0, 2, 2)] - [InlineData (-1, -1, 2, 2)] - [SetupFakeApplication] - public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) - { - var superView = new View { App = ApplicationImpl.Instance, Width = Dim.Fill (), Height = Dim.Fill () }; - - var view = new View - { - Text = "X", - X = 1, - Y = 1, - Width = 3, - Height = 3, - BorderStyle = LineStyle.Single - }; - superView.Add (view); - superView.BeginInit (); - superView.EndInit (); - superView.LayoutSubViews (); - - superView.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │X│ - └─┘", - _output); - - Rectangle toFill = new (x, y, width, height); - superView.SetClipToScreen (); - view.FillRect (toFill); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │ │ - └─┘", - _output); - - // Now try to clear beyond Viewport (invalid; clipping should prevent) - superView.SetNeedsDraw (); - superView.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │X│ - └─┘", - _output); - toFill = new Rectangle (-width, -height, width, height); - view.FillRect (toFill); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │X│ - └─┘", - _output); - - // Now try to clear beyond Viewport (valid) - superView.SetNeedsDraw (); - superView.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │X│ - └─┘", - _output); - toFill = new Rectangle (-1, -1, width + 1, height + 1); - - superView.SetClipToScreen (); - view.FillRect (toFill); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │ │ - └─┘", - _output); - - // Now clear too much size - superView.SetNeedsDraw (); - superView.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │X│ - └─┘", - _output); - toFill = new Rectangle (0, 0, width * 2, height * 2); - superView.SetClipToScreen (); - view.FillRect (toFill); - - DriverAssert.AssertDriverContentsWithFrameAre (@" - ┌─┐ - │ │ - └─┘", - _output); - } - - // TODO: Simplify this test to just use AddRune directly - [Fact] - [SetupFakeApplication] - [Trait ("Category", "Unicode")] - public void Clipping_Wide_Runes () - { - Application.Driver!.SetScreenSize (30, 1); - Application.Driver!.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①'); - - var top = new View { App = ApplicationImpl.Instance, Id = "top", Width = Dim.Fill (), Height = Dim.Fill () }; - - var frameView = new View - { - Id = "frameView", - Width = Dim.Fill (), - Height = Dim.Fill (), - Text = """ - これは広いルーンラインです。 - """ - }; - frameView.Border.LineStyle = LineStyle.Single; - frameView.Border.Thickness = new Thickness (1, 0, 0, 0); - - top.Add (frameView); - top.SetClipToScreen (); - top.Layout (); - top.Draw (); - - var expectedOutput = """ - │これは広いルーンラインです。 - """; - - DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output); - - var view = new View - { - Text = "0123456789", - - //Text = "ワイドルー。", - X = 2, - Height = Dim.Auto (), - Width = Dim.Auto (), - BorderStyle = LineStyle.Single - }; - view.Border.Thickness = new Thickness (1, 0, 1, 0); - - top.Add (view); - top.Layout (); - top.SetClipToScreen (); - top.Draw (); - - // 012345678901234567890123456789012345678 - // 012 34 56 78 90 12 34 56 78 90 12 34 56 78 - // │こ れ は 広 い ル ー ン ラ イ ン で す 。 - // 01 2345678901234 56 78 90 12 34 56 - // │① |0123456989│① ン ラ イ ン で す 。 - expectedOutput = """ - │①│0123456789│ ンラインです。 - """; - - DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output); - } - - // TODO: Add more AddRune tests to cover all the cases where wide runes are clipped - - [Fact] - [SetupFakeApplication] - public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped () - { - // Screen is 25x25 - // View is 25x25 - // Viewport is (0, 0, 23, 23) - // ContentSize is (10, 10) - // ViewportToScreen is (1, 1, 23, 23) - // Visible content is (1, 1, 10, 10) - // Expected clip is (1, 1, 10, 10) - same as visible content - Rectangle expectedClip = new (1, 1, 10, 10); - - // Arrange - var view = new View - { - Width = Dim.Fill (), Height = Dim.Fill (), ViewportSettings = ViewportSettingsFlags.ClipContentOnly, App = ApplicationImpl.Instance - }; - view.SetContentSize (new Size (10, 10)); - view.Border.Thickness = new Thickness (1); - view.BeginInit (); - view.EndInit (); - Assert.Equal (view.Frame, view.GetClip ()!.GetBounds ()); - - // Act - view.AddViewportToClip (); - - // Assert - Assert.Equal (expectedClip, view.GetClip ()!.GetBounds ()); - view.Dispose (); - } - - [Fact] - [SetupFakeApplication] - public void SetClip_Default_ClipsToViewport () - { - // Screen is 25x25 - Application.Driver!.SetScreenSize (25, 25); - - // View is 25x25 - // Viewport is (0, 0, 23, 23) - // ContentSize is (10, 10) - // ViewportToScreen is (1, 1, 23, 23) - // Visible content is (1, 1, 10, 10) - // Expected clip is (1, 1, 23, 23) - same as Viewport - Rectangle expectedClip = new (1, 1, 23, 23); - - // Arrange - var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), App = ApplicationImpl.Instance }; - view.SetContentSize (new Size (10, 10)); - view.Border.Thickness = new Thickness (1); - view.BeginInit (); - view.EndInit (); - Assert.Equal (view.Frame, view.GetClip ()!.GetBounds ()); - view.Viewport = view.Viewport with { X = 1, Y = 1 }; - - // Act - view.AddViewportToClip (); - - // Assert - Assert.Equal (expectedClip, view.GetClip ()!.GetBounds ()); - view.Dispose (); - } -} diff --git a/Tests/UnitTests/View/Draw/DrawEventTests.cs b/Tests/UnitTests/View/Draw/DrawEventTests.cs deleted file mode 100644 index c037af3d57..0000000000 --- a/Tests/UnitTests/View/Draw/DrawEventTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -#nullable enable -using UnitTests; - -namespace UnitTests.ViewBaseTests; - -[Trait ("Category", "Output")] -public class DrawEventTests -{ - [Fact] - [AutoInitShutdown] - public void DrawContentComplete_Event_Is_Always_Called () - { - var viewCalled = false; - var tvCalled = false; - - var view = new View { Width = 10, Height = 10, Text = "View" }; - view.DrawComplete += (s, e) => viewCalled = true; - var tv = new TextView { Y = 11, Width = 10, Height = 10 }; - tv.DrawComplete += (s, e) => tvCalled = true; - - var top = new Runnable (); - top.Add (view, tv); - Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - - Assert.True (viewCalled); - Assert.True (tvCalled); - top.Dispose (); - } -} diff --git a/Tests/UnitTests/View/Draw/DrawTests.cs b/Tests/UnitTests/View/Draw/DrawTests.cs deleted file mode 100644 index a4492cba5b..0000000000 --- a/Tests/UnitTests/View/Draw/DrawTests.cs +++ /dev/null @@ -1,953 +0,0 @@ -#nullable enable -using System.Text; - -namespace UnitTests.ViewBaseTests; - -[Trait ("Category", "Output")] -public class DrawTests (ITestOutputHelper output) -{ - - [Fact] - [AutoInitShutdown] - [Trait ("Category", "Unicode")] - public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two () - { - const string s = "\U0000f900"; - var r = (Rune)0xf900; - - Assert.Equal ("豈", s); - Assert.Equal ("豈", r.ToString ()); - Assert.Equal (s, r.ToString ()); - - Assert.Equal (2, s.GetColumns ()); - Assert.Equal (2, r.GetColumns ()); - - var win = new Window { Title = s }; - var view = new View { Text = r.ToString (), Height = Dim.Fill (), Width = Dim.Fill () }; - var tf = new TextField { Text = s, Y = 1, Width = 3 }; - win.Add (view, tf); - Runnable top = new (); - top.Add (win); - - Application.Begin (top); - Application.Driver!.SetScreenSize (10, 4); - Application.LayoutAndDraw (); - - const string expectedOutput = """ - - ┌┤豈├────┐ - │豈 │ - │豈 │ - └────────┘ - """; - DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, output); - - DriverAssert.AssertDriverContentsAre (expectedOutput, output); - - // This test has nothing to do with color - removing as it is not relevant and fragile - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - [Trait ("Category", "Output")] - public void Colors_On_TextAlignment_Right_And_Bottom () - { - var viewRight = new View - { - Text = "Test", - Width = 6, - Height = 1, - TextAlignment = Alignment.End, - }; - - var viewBottom = new View - { - Text = "Test", - TextDirection = TextDirection.TopBottom_LeftRight, - Y = 1, - Width = 1, - Height = 6, - VerticalTextAlignment = Alignment.End, - }; - Runnable top = new (); - top.Add (viewRight, viewBottom); - - var rs = Application.Begin (top); - Application.Driver!.SetScreenSize (7, 7); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - Test - - - T - e - s - t - """, - output - ); - - DriverAssert.AssertDriverAttributesAre ( - """ - - 000000 - 0 - 0 - 0 - 0 - 0 - 0 - """, - output, - Application.Driver, - SchemeManager.GetSchemes () ["Base"]!.Normal - ); - top.Dispose (); - } - - [Fact] - [SetupFakeApplication] - public void Draw_Minimum_Full_Border_With_Empty_Viewport () - { - var view = new View - { - App = ApplicationImpl.Instance, - Width = 2, Height = 2, BorderStyle = LineStyle.Single - }; - Assert.True (view.NeedsLayout); - Assert.True (view.NeedsDraw); - view.Layout (); - - Assert.Equal (new (0, 0, 2, 2), view.Frame); - Assert.Equal (Rectangle.Empty, view.Viewport); - - Assert.True (view.NeedsDraw); - view.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - ┌┐ - └┘ - """, - output - ); - } - - [Fact] - [SetupFakeApplication] - public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom () - { - var view = new View - { - App = ApplicationImpl.Instance, - Width = 2, Height = 1, BorderStyle = LineStyle.Single - }; - view.Border.Thickness = new (1, 1, 1, 0); - view.BeginInit (); - view.EndInit (); - view.SetRelativeLayout (Application.Screen.Size); - - Assert.Equal (new (0, 0, 2, 1), view.Frame); - Assert.Equal (Rectangle.Empty, view.Viewport); - - view.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ("──", output); - } - - [Fact] - [SetupFakeApplication] - public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left () - { - var view = new View - { - App = ApplicationImpl.Instance, - Width = 1, Height = 2, BorderStyle = LineStyle.Single - }; - view.Border.Thickness = new (0, 1, 1, 1); - view.BeginInit (); - view.EndInit (); - view.SetRelativeLayout (Application.Screen.Size); - - Assert.Equal (new (0, 0, 1, 2), view.Frame); - Assert.Equal (Rectangle.Empty, view.Viewport); - - view.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - │ - │ - """, - output - ); - } - - [Fact] - [SetupFakeApplication] - public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right () - { - var view = new View - { - App = ApplicationImpl.Instance, - Width = 1, Height = 2, BorderStyle = LineStyle.Single - }; - view.Border.Thickness = new (1, 1, 0, 1); - view.BeginInit (); - view.EndInit (); - view.SetRelativeLayout (Application.Screen.Size); - - Assert.Equal (new (0, 0, 1, 2), view.Frame); - Assert.Equal (Rectangle.Empty, view.Viewport); - - view.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - │ - │ - """, - output - ); - } - - [Fact] - [SetupFakeApplication] - public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top () - { - var view = new View - { - App = ApplicationImpl.Instance, - Width = 2, Height = 1, BorderStyle = LineStyle.Single - }; - view.Border.Thickness = new (1, 0, 1, 1); - - view.BeginInit (); - view.EndInit (); - view.SetRelativeLayout (Application.Screen.Size); - - Assert.Equal (new (0, 0, 2, 1), view.Frame); - Assert.Equal (Rectangle.Empty, view.Viewport); - - view.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - "││", - output - ); - } - - [Fact] - [AutoInitShutdown] - public void Draw_Negative_Viewport_Horizontal_With_New_Lines () - { - var subView = new View - { - Id = "subView", - X = 1, - Width = 1, - Height = 7, - Text = """ - s - u - b - V - i - e - w - """ - }; - - var view = new View - { - Id = "view", Width = 2, Height = 20, Text = """ - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - """ - }; - view.Add (subView); - var content = new View { Id = "content", Width = 20, Height = 20 }; - content.Add (view); - - var container = new View - { - Id = "container", - X = 1, - Y = 1, - Width = 5, - Height = 5 - }; - container.Add (content); - Runnable top = new (); - top.Add (container); - var rs = Application.Begin (top); - - top.Draw (); - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 0s - 1u - 2b - 3V - 4i - """, - output - ); - - content.X = -1; - - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - s - u - b - V - i - """, - output - ); - - content.X = -2; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre (@"", output); - - content.X = 0; - content.Y = -1; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 1u - 2b - 3V - 4i - 5e - """, - output - ); - - content.Y = -6; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 6w - 7 - 8 - 9 - 0 - """, - output - ); - - content.Y = -19; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 9 - """, - output - ); - - content.Y = -20; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - - content.X = -2; - content.Y = 0; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Draw_Negative_Viewport_Horizontal_Without_New_Lines () - { - // BUGBUG: This previously assumed the default height of a View was 1. - var subView = new View - { - Id = "subView", - Y = 1, - Width = 7, - Height = 1, - Text = "subView" - }; - var view = new View { Id = "view", Width = 20, Height = 2, Text = "01234567890123456789" }; - view.Add (subView); - var content = new View { Id = "content", Width = 20, Height = 20 }; - content.Add (view); - - var container = new View - { - Id = "container", - X = 1, - Y = 1, - Width = 5, - Height = 5 - }; - container.Add (content); - Runnable top = new (); - top.Add (container); - - // BUGBUG: v2 - it's bogus to reference .Frame before BeginInit. And why is the clip being set anyway??? - - top.SubViewsLaidOut += Top_LayoutComplete; - Application.Begin (top); - - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 01234 - subVi - """, - output - ); - - content.X = -1; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 12345 - ubVie - """, - output - ); - - content.Y = -1; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - ubVie - """, - output - ); - - content.Y = -2; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - - content.X = -20; - content.Y = 0; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - top.Dispose (); - - return; - - void Top_LayoutComplete (object? sender, LayoutEventArgs e) { Application.Driver!.Clip = new (container.Frame); } - } - - [Fact] - [AutoInitShutdown] - public void Draw_Negative_Viewport_Vertical () - { - var subView = new View - { - Id = "subView", - X = 1, - Width = 1, - Height = 7, - Text = "subView", - TextDirection = TextDirection.TopBottom_LeftRight - }; - - var view = new View - { - Id = "view", - Width = 2, - Height = 20, - Text = "01234567890123456789", - TextDirection = TextDirection.TopBottom_LeftRight - }; - view.Add (subView); - var content = new View { Id = "content", Width = 20, Height = 20 }; - content.Add (view); - - var container = new View - { - Id = "container", - X = 1, - Y = 1, - Width = 5, - Height = 5 - }; - container.Add (content); - Runnable top = new (); - top.Add (container); - Application.Begin (top); - - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 0s - 1u - 2b - 3V - 4i - """, - output - ); - - content.X = -1; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - s - u - b - V - i - """, - output - ); - - content.X = -2; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre (@"", output); - - content.X = 0; - content.Y = -1; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 1u - 2b - 3V - 4i - 5e - """, - output - ); - - content.Y = -6; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 6w - 7 - 8 - 9 - 0 - """, - output - ); - - content.Y = -19; - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - """ - - 9 - """, - output - ); - - content.Y = -20; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - - content.X = -2; - content.Y = 0; - AutoInitShutdownAttribute.RunIteration (); - DriverAssert.AssertDriverContentsWithFrameAre ("", output); - top.Dispose (); - } - - [Theory] - [SetupFakeApplication] - [InlineData ("𝔽𝕆𝕆𝔹𝔸R")] - [InlineData ("a𐐀b")] - public void DrawHotString_NonBmp (string expected) - { - var view = new View - { - App = ApplicationImpl.Instance, - Width = 10, Height = 1 - }; - view.DrawHotString (expected, Attribute.Default, Attribute.Default); - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - } - - // TODO: The tests below that use Label should use View instead. - [Fact] - [AutoInitShutdown] - public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () - { - var us = "\U0001d539"; - var r = (Rune)0x1d539; - - Assert.Equal ("𝔹", us); - Assert.Equal ("𝔹", r.ToString ()); - Assert.Equal (us, r.ToString ()); - - Assert.Equal (1, us.GetColumns ()); - Assert.Equal (1, r.GetColumns ()); - - var win = new Window { Title = us }; - var view = new Label { Text = r.ToString () }; - var tf = new TextField { Text = us, Y = 1, Width = 3 }; - win.Add (view, tf); - Runnable top = new (); - top.Add (win); - - Application.Begin (top); - Application.Driver!.SetScreenSize (10, 4); - Application.LayoutAndDraw (); - - var expected = """ - - ┌┤𝔹├─────┐ - │𝔹 │ - │𝔹 │ - └────────┘ - """; - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - DriverAssert.AssertDriverContentsAre (expected, output); - top.Dispose (); - - // This test has nothing to do with color - removing as it is not relevant and fragile - } - - [Fact] - [AutoInitShutdown] - public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds () - { - Runnable top = new (); - - var view = new View { X = -2, Text = "view" }; - top.Add (view); - - Application.Iteration += OnApplicationOnIteration; - - try - { - Application.Run (top); - } - catch (IndexOutOfRangeException ex) - { - // After the fix this exception will not be caught. - Assert.IsType (ex); - } - finally - { - Application.Iteration -= OnApplicationOnIteration; - } - - top.Dispose (); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - - return; - - void OnApplicationOnIteration (object? s, EventArgs a) - { - Assert.Equal (-2, view.X); - - Application.RequestStop (); - } - } - - - [Fact] - [AutoInitShutdown] - public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame () - { - var label = new Label { Text = "At 0,0" }; - - var view = new DerivedView - { - X = 2, - Y = 2, - Width = 30, - Height = 2, - Text = "A text with some long width\n and also with two lines." - }; - Runnable top = new (); - top.Add (label, view); - SessionToken sessionToken = Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - - A text with some long width - and also with two lines. " - , - output - ); - - view.Frame = new (3, 3, 10, 1); - Assert.Equal (new (3, 3, 10, 1), view.Frame); - Assert.Equal (new (0, 0, 10, 1), view.Viewport); - Assert.Equal (new (0, 0, 10, 1), view.NeedsDrawRect); - //Application.Refresh(); - top.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - - - A text wit", - output - ); - Application.End (sessionToken); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim () - { - var label = new Label { Text = "At 0,0" }; - - var view = new DerivedView - { - X = 2, - Y = 2, - Width = 30, - Height = 2, - Text = "A text with some long width\n and also with two lines." - }; - Runnable top = new (); - top.Add (label, view); - SessionToken sessionToken = Application.Begin (top); - - top.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - - A text with some long width - and also with two lines. " - , - output - ); - - view.X = 3; - view.Y = 3; - view.Width = 10; - view.Height = 1; - Assert.Equal (new (3, 3, 10, 1), view.Frame); - Assert.Equal (new (0, 0, 10, 1), view.Viewport); - Assert.Equal (new (0, 0, 10, 1), view.NeedsDrawRect); - view.SetClipToScreen (); - top.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - - - A text wit" - , - output - ); - Application.End (sessionToken); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame () - { - var label = new Label { Text = "At 0,0" }; - - var view = new DerivedView - { - X = 2, - Y = 2, - Width = 30, - Height = 2, - Text = "A text with some long width\n and also with two lines." - }; - Runnable top = new (); - top.Add (label, view); - SessionToken sessionToken = Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - - A text with some long width - and also with two lines. " - , - output - ); - - view.Frame = new (1, 1, 10, 1); - Assert.Equal (new (1, 1, 10, 1), view.Frame); - Assert.Equal (new (0, 0, 10, 1), view.Viewport); - Assert.Equal (new (0, 0, 10, 1), view.NeedsDrawRect); - top.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - A text wit" - , - output - ); - Application.End (sessionToken); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim () - { - var label = new Label { Text = "At 0,0" }; - - var view = new DerivedView - { - X = 2, - Y = 2, - Width = 30, - Height = 2, - Text = "A text with some long width\n and also with two lines." - }; - Runnable top = new (); - top.Add (label, view); - SessionToken sessionToken = Application.Begin (top); - - top.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - - A text with some long width - and also with two lines. " - , - output - ); - - view.X = 1; - view.Y = 1; - view.Width = 10; - view.Height = 1; - Assert.Equal (new (1, 1, 10, 1), view.Frame); - Assert.Equal (new (0, 0, 10, 1), view.Viewport); - Assert.Equal (new (0, 0, 10, 1), view.NeedsDrawRect); - view.SetClipToScreen (); - - top.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -At 0,0 - A text wit" - , - output - ); - Application.End (sessionToken); - top.Dispose (); - } - public class DerivedView : View - { - public DerivedView () { CanFocus = true; } - public bool IsKeyDown { get; set; } - public bool IsKeyPress { get; set; } - public override string Text { get; set; } = null!; - - protected override bool OnDrawingContent (DrawContext? context) - { - var idx = 0; - - // BUGBUG: v2 - this should use Viewport, not Frame - for (var r = 0; r < Frame.Height; r++) - { - for (var c = 0; c < Frame.Width; c++) - { - if (idx < Text.Length) - { - char rune = Text [idx]; - - if (rune != '\n') - { - AddRune (c, r, (Rune)Text [idx]); - } - - idx++; - - if (rune == '\n') - { - break; - } - } - } - } - - ClearNeedsDraw (); - - return true; - } - - protected override bool OnKeyDown (Key keyEvent) - { - IsKeyDown = true; - - return true; - } - - protected override bool OnKeyDownNotHandled (Key keyEvent) - { - IsKeyPress = true; - - return true; - } - } -} diff --git a/Tests/UnitTests/View/Draw/NeedsDrawTests.cs b/Tests/UnitTests/View/Draw/NeedsDrawTests.cs deleted file mode 100644 index 231590f725..0000000000 --- a/Tests/UnitTests/View/Draw/NeedsDrawTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -#nullable enable -using UnitTests; - -namespace UnitTests.ViewBaseTests; - -[Trait ("Category", "Output")] -public class NeedsDrawTests () -{ - [Fact] - [AutoInitShutdown] - public void Frame_Set_After_Initialize_Update_NeededDisplay () - { - var frame = new FrameView (); - - var label = new Label - { - SchemeName = "Menu", X = 0, Y = 0, Text = "This should be the first line." - }; - - var view = new View - { - X = 0, // don't overcomplicate unit tests - Y = 1, - Height = Dim.Auto (DimAutoStyle.Text), - Width = Dim.Auto (DimAutoStyle.Text), - Text = "Press me!" - }; - - frame.Add (label, view); - - frame.X = Pos.Center (); - frame.Y = Pos.Center (); - frame.Width = 40; - frame.Height = 8; - - Runnable top = new (); - - top.Add (frame); - - SessionToken sessionToken = Application.Begin (top); - - Application.Driver!.SetScreenSize (80,25); - - top.SubViewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 80, 25), top.NeedsDrawRect); }; - - frame.SubViewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 40, 8), frame.NeedsDrawRect); }; - - label.SubViewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 38, 1), label.NeedsDrawRect); }; - - view.SubViewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 13, 1), view.NeedsDrawRect); }; - Assert.Equal (new (0, 0, 80, 25), top.Frame); - Assert.Equal (new (20, 8, 40, 8), frame.Frame); - - Assert.Equal ( - new (20, 8, 60, 16), - new Rectangle ( - frame.Frame.Left, - frame.Frame.Top, - frame.Frame.Right, - frame.Frame.Bottom - ) - ); - Assert.Equal (new (0, 0, 30, 1), label.Frame); - Assert.Equal (new (0, 1, 9, 1), view.Frame); // this proves frame was set - Application.End (sessionToken); - top.Dispose (); - } -} diff --git a/Tests/UnitTests/View/Draw/TransparentTests.cs b/Tests/UnitTests/View/Draw/TransparentTests.cs deleted file mode 100644 index a4929c1cf9..0000000000 --- a/Tests/UnitTests/View/Draw/TransparentTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -#nullable enable - -namespace UnitTests.ViewBaseTests; - -[Trait ("Category", "Output")] -public class TransparentTests (ITestOutputHelper output) -{ - [Fact] - [SetupFakeApplication] - - public void Transparent_Text_Occludes () - { - var super = new View - { - App = ApplicationImpl.Instance, - Id = "super", - Width = 20, - Height = 5, - }; - super.DrawingContent += (sender, args) => - { - var s = sender as View; - s!.FillRect(s!.Viewport, Glyphs.Stipple); - args.Cancel = true; - }; - - var sub = new View - { - X = 1, - Y = 1, - Width = 15, - Height = 3, - Id = "sub", - Text = "Sub", - ViewportSettings = ViewportSettingsFlags.Transparent, - BorderStyle = LineStyle.Single - }; - - super.Add (sub); - - super.Layout (); - super.Draw (); - - _ = DriverAssert.AssertDriverContentsWithFrameAre ( - @" -░░░░░░░░░░░░░░░░░░░░ -░┌─────────────┐░░░░ -░│Sub░░░░░░░░░░│░░░░ -░└─────────────┘░░░░ -░░░░░░░░░░░░░░░░░░░░", output); - } - - [Fact] - [SetupFakeApplication] - - public void Transparent_SubView_Occludes () - { - var super = new View - { - App = ApplicationImpl.Instance, - Id = "super", - Width = 20, - Height = 5, - }; - super.DrawingContent += (sender, args) => - { - var s = sender as View; - s!.FillRect (s!.Viewport, Glyphs.Stipple); - args.Cancel = true; - }; - - var sub = new View - { - X = 1, - Y = 1, - Width = 15, - Height = 3, - Id = "sub", - ViewportSettings = ViewportSettingsFlags.Transparent, - BorderStyle = LineStyle.Single - }; - - var subSub = new View - { - X = Pos.Center(), - Y = Pos.Center(), - Width = Dim.Auto(), - Height = Dim.Auto(), - Id = "subSub", - Text = "subSub", - }; - sub.Add (subSub); - - super.Add (sub); - - super.Layout (); - super.Draw (); - - _ = DriverAssert.AssertDriverContentsWithFrameAre ( - @" -░░░░░░░░░░░░░░░░░░░░ -░┌─────────────┐░░░░ -░│░░░subSub░░░░│░░░░ -░└─────────────┘░░░░ -░░░░░░░░░░░░░░░░░░░░", output); - } -} diff --git a/Tests/UnitTests/View/Layout/Dim.Tests.cs b/Tests/UnitTests/View/Layout/Dim.Tests.cs deleted file mode 100644 index 331edd1567..0000000000 --- a/Tests/UnitTests/View/Layout/Dim.Tests.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System.Globalization; -using System.Text; -using static Terminal.Gui.ViewBase.Dim; - -namespace UnitTests.LayoutTests; - -public class DimTests -{ - private readonly ITestOutputHelper _output; - - public DimTests (ITestOutputHelper output) - { - _output = output; - Console.OutputEncoding = Encoding.Default; - - // Change current culture - var culture = CultureInfo.CreateSpecificCulture ("en-US"); - Thread.CurrentThread.CurrentCulture = culture; - Thread.CurrentThread.CurrentUICulture = culture; - } - - - // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved - // TODO: A new test that calls SetRelativeLayout directly is needed. - [Fact (Skip = "Convoluted test; rewrite")] - [AutoInitShutdown] - public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height () - { - // Override CM - Button.DefaultShadow = ShadowStyles.None; - - // Testing with the Button because it properly handles the Dim class. - Runnable t = new (); - - var w = new Window { Width = 100, Height = 100 }; - - var f1 = new FrameView - { - X = 0, - Y = 0, - Width = Percent (50), - Height = 5, - Title = "f1" - }; - - var f2 = new FrameView - { - X = Pos.Right (f1), - Y = 0, - Width = Fill (), - Height = 5, - Title = "f2" - }; - - var v1 = new Button - { - X = Pos.X (f1) + 2, - Y = Pos.Bottom (f1) + 2, - Width = Width (f1) - 2, - Height = Fill () - 2, - ValidatePosDim = true, - Text = "v1" - }; - - var v2 = new Button - { - X = Pos.X (f2) + 2, - Y = Pos.Bottom (f2) + 2, - Width = Width (f2) - 2, - Height = Fill () - 2, - ValidatePosDim = true, - Text = "v2" - }; - - var v3 = new Button - { - Width = Percent (10), - Height = Percent (10), - ValidatePosDim = true, - Text = "v3" - }; - - var v4 = new Button - { - Width = Absolute (50), - Height = Absolute (50), - ValidatePosDim = true, - Text = "v4" - }; - - var v5 = new Button - { - Width = Width (v1) - Width (v3), - Height = Height (v1) - Height (v3), - ValidatePosDim = true, - Text = "v5" - }; - - var v6 = new Button - { - X = Pos.X (f2), - Y = Pos.Bottom (f2) + 2, - Width = Percent (20, DimPercentMode.Position), - Height = Percent (20, DimPercentMode.Position), - ValidatePosDim = true, - Text = "v6" - }; - - w.Add (f1, f2, v1, v2, v3, v4, v5, v6); - t.Add (w); - - t.IsModalChanged += (s, e) => - { - Assert.Equal ("Absolute(100)", w.Width.ToString ()); - Assert.Equal ("Absolute(100)", w.Height.ToString ()); - Assert.Equal (100, w.Frame.Width); - Assert.Equal (100, w.Frame.Height); - - Assert.Equal ("Absolute(5)", f1.Height.ToString ()); - Assert.Equal (49, f1.Frame.Width); // 50-1=49 - Assert.Equal (5, f1.Frame.Height); - - Assert.Equal ("Fill(Absolute(0))", f2.Width.ToString ()); - Assert.Equal ("Absolute(5)", f2.Height.ToString ()); - Assert.Equal (49, f2.Frame.Width); // 50-1=49 - Assert.Equal (5, f2.Frame.Height); - - Assert.Equal ($"Combine(View(Width,FrameView(){f1.Border.GetFrame ()})-Absolute(2))", v1.Width.ToString ()); - Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v1.Height.ToString ()); - Assert.Equal (47, v1.Frame.Width); // 49-2=47 - Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89 - - Assert.Equal ( - $"Combine(View(Width,FrameView(){f2.Frame})-Absolute(2))", - v2.Width.ToString () - ); - Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v2.Height.ToString ()); - Assert.Equal (47, v2.Frame.Width); // 49-2=47 - Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89 - - Assert.Equal (9, v3.Frame.Width); // 98*10%=9 - Assert.Equal (9, v3.Frame.Height); // 98*10%=9 - - Assert.Equal ("Absolute(50)", v4.Width.ToString ()); - Assert.Equal ("Absolute(50)", v4.Height.ToString ()); - Assert.Equal (50, v4.Frame.Width); - Assert.Equal (50, v4.Frame.Height); - Assert.Equal ($"Combine(View(Height,Button(){v1.Frame})-View(Height,Button(){v3.Viewport}))", v5.Height.ToString ( )); - Assert.Equal (38, v5.Frame.Width); // 47-9=38 - Assert.Equal (80, v5.Frame.Height); // 89-9=80 - - Assert.Equal (9, v6.Frame.Width); // 47*20%=9 - Assert.Equal (18, v6.Frame.Height); // 89*20%=18 - - w.Width = 200; - Assert.True (t.NeedsLayout); - w.Height = 200; - t.LayoutSubViews (); - - Assert.Equal ("Absolute(200)", w.Width.ToString ()); - Assert.Equal ("Absolute(200)", w.Height.ToString ()); - Assert.Equal (200, w.Frame.Width); - Assert.Equal (200, w.Frame.Height); - - f1.Text = "Frame1"; - Assert.Equal (99, f1.Frame.Width); // 100-1=99 - Assert.Equal (5, f1.Frame.Height); - - f2.Text = "Frame2"; - Assert.Equal ("Fill(Absolute(0))", f2.Width.ToString ()); - Assert.Equal ("Absolute(5)", f2.Height.ToString ()); - Assert.Equal (99, f2.Frame.Width); // 100-1=99 - Assert.Equal (5, f2.Frame.Height); - - v1.Text = "LeftButton"; - Assert.Equal ($"Combine(View(Width,FrameView(){f1.Frame})-Absolute(2))", v1.Width.ToString ()); - Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v1.Height.ToString ()); - Assert.Equal (97, v1.Frame.Width); // 99-2=97 - Assert.Equal (189, v1.Frame.Height); // 198-2-7=189 - - v2.Text = "MiddleButton"; - - Assert.Equal ($"Combine(View(Width,FrameView(){f2.Frame})-Absolute(2))", v2.Width.ToString ()); - Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v2.Height.ToString ()); - Assert.Equal (97, v2.Frame.Width); // 99-2=97 - Assert.Equal (189, v2.Frame.Height); // 198-2-7=189 - - v3.Text = "RightButton"; - - // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width - Assert.Equal (19, v3.Frame.Width); - - // 199*10%=19 - Assert.Equal (19, v3.Frame.Height); - - v4.Text = "Button4"; - v4.Width = Auto (DimAutoStyle.Text); - v4.Height = Auto (DimAutoStyle.Text); - v4.Layout (); - Assert.Equal (Auto (DimAutoStyle.Text), v4.Width); - Assert.Equal (Auto (DimAutoStyle.Text), v4.Height); - Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is DimAbsolute - Assert.Equal (1, v4.Frame.Height); // 1 because is DimAbsolute - - v5.Text = "Button5"; - - Assert.Equal ($"Combine(View(Width,Button(){v1.Frame})-View(Width,Button(){v3.Frame}))", v5.Width.ToString ()); - Assert.Equal ($"Combine(View(Height,Button(){v1.Frame})-View(Height,Button(){v3.Frame}))", v5.Height.ToString ()); - - Assert.Equal (78, v5.Frame.Width); // 97-9=78 - Assert.Equal (170, v5.Frame.Height); // 189-19=170 - - v6.Text = "Button6"; - Assert.Equal (19, v6.Frame.Width); // 99*20%=19 - Assert.Equal (38, v6.Frame.Height); // 198-7*20=18 - }; - - Application.StopAfterFirstIteration = true; - - Application.Run (t); - t.Dispose (); - } -} diff --git a/Tests/UnitTests/View/Layout/Pos.Tests.cs b/Tests/UnitTests/View/Layout/Pos.Tests.cs deleted file mode 100644 index 76d8bb00a6..0000000000 --- a/Tests/UnitTests/View/Layout/Pos.Tests.cs +++ /dev/null @@ -1,250 +0,0 @@ -#nullable enable - -namespace UnitTests.LayoutTests; - -public class PosTests () -{ - [Fact] - public void - Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () - { - Application.Init (DriverRegistry.Names.ANSI); - - Runnable t = new (); - - var w = new Window { X = Pos.Left (t) + 2, Y = Pos.Absolute (2) }; - - var v = new View { X = Pos.Center (), Y = Pos.Percent (10) }; - - w.Add (v); - t.Add (w); - - t.IsModalChanged += (s, e) => - { - v.Frame = new Rectangle (2, 2, 10, 10); - Assert.Equal (2, v.X = 2); - Assert.Equal (2, v.Y = 2); - }; - - Application.StopAfterFirstIteration = true; - - Application.Run (t); - t.Dispose (); - Application.Shutdown (); - } - - - // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved - // TODO: A new test that calls SetRelativeLayout directly is needed. - [Fact] - public void PosCombine_WHY_Throws () - { - Application.Init (DriverRegistry.Names.ANSI); - - Runnable t = new Runnable (); - - var w = new Window { X = Pos.Left (t) + 2, Y = Pos.Top (t) + 2 }; - var f = new FrameView (); - var v1 = new View { X = Pos.Left (w) + 2, Y = Pos.Top (w) + 2 }; - var v2 = new View { X = Pos.Left (v1) + 2, Y = Pos.Top (v1) + 2 }; - - f.Add (v1); // v2 not added - w.Add (f); - t.Add (w); - - f.X = Pos.X (v2) - Pos.X (v1); - f.Y = Pos.Y (v2) - Pos.Y (v1); - - Assert.Throws (() => Application.Run (t)); - t.Dispose (); - Application.Shutdown (); - - v2.Dispose (); - } - - - // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved - // TODO: A new test that calls SetRelativeLayout directly is needed. - [Fact] - [SetupFakeApplication] - public void Pos_Add_Operator () - { - Runnable top = new (); - - var view = new View { X = 0, Y = 0, Width = 20, Height = 20 }; - var field = new TextField { X = 0, Y = 0, Width = 20 }; - var count = 0; - - field.KeyDown += (s, k) => - { - if (k.KeyCode == KeyCode.Enter) - { - field.Text = $"View {count}"; - var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text }; - view.Add (view2); - Assert.Equal ($"View {count}", view2.Text); - Assert.Equal ($"Absolute({count})", view2.Y.ToString ()); - - Assert.Equal ($"Absolute({count})", field.Y.ToString ()); - field.Y += 1; - count++; - Assert.Equal ($"Absolute({count})", field.Y.ToString ()); - } - }; - - Application.Iteration += OnInstanceOnIteration; - - var win = new Window (); - win.Add (view); - win.Add (field); - - top.Add (win); - - Application.Run (top); - Application.Iteration -= OnInstanceOnIteration; - - Assert.Equal (20, count); - - top.Dispose (); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - - return; - - void OnInstanceOnIteration (object? s, EventArgs a) - { - while (count < 20) - { - field.NewKeyDownEvent (Key.Enter); - } - - Application.RequestStop (); - } - } - - // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved - // TODO: A new test that calls SetRelativeLayout directly is needed. - [Fact] - public void Pos_Subtract_Operator () - { - Application.Init (DriverRegistry.Names.ANSI); - - Runnable top = new (); - - var view = new View { X = 0, Y = 0, Width = 20, Height = 20 }; - var field = new TextField { X = 0, Y = 0, Width = 20 }; - var count = 20; - List listViews = new (); - - for (var i = 0; i < count; i++) - { - field.Text = $"View {i}"; - var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text }; - view.Add (view2); - Assert.Equal ($"View {i}", view2.Text); - Assert.Equal ($"Absolute({i})", field.Y.ToString ()); - listViews.Add (view2); - - Assert.Equal ($"Absolute({i})", field.Y.ToString ()); - field.Y += 1; - Assert.Equal ($"Absolute({i + 1})", field.Y.ToString ()); - } - - field.KeyDown += (s, k) => - { - if (k.KeyCode == KeyCode.Enter) - { - Assert.Equal ($"View {count - 1}", listViews [count - 1].Text); - view.Remove (listViews [count - 1]); - listViews [count - 1].Dispose (); - - Assert.Equal ($"Absolute({count})", field.Y.ToString ()); - field.Y -= 1; - count--; - Assert.Equal ($"Absolute({count})", field.Y.ToString ()); - } - }; - - Application.Iteration += OnApplicationOnIteration; - - var win = new Window (); - win.Add (view); - win.Add (field); - - top.Add (win); - - Application.Run (top); - Application.Iteration -= OnApplicationOnIteration; - - Assert.Equal (0, count); - - top.Dispose (); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - - return; - - void OnApplicationOnIteration (object? s, EventArgs a) - { - while (count > 0) - { - field.NewKeyDownEvent (Key.Enter); - } - - Application.RequestStop (); - } - } - - // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved - // TODO: A new test that calls SetRelativeLayout directly is needed. - [Fact] - public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null () - { - Application.Init (DriverRegistry.Names.ANSI); - - Runnable t = new (); - - var w = new Window { X = 1, Y = 2, Width = 3, Height = 5 }; - t.Add (w); - - t.IsModalChanged += (s, e) => - { - Assert.Equal (2, w.X = 2); - Assert.Equal (2, w.Y = 2); - }; - - Application.StopAfterFirstIteration = true; - - Application.Run (t); - t.Dispose (); - Application.Shutdown (); - } - - - // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved - // TODO: A new test that calls SetRelativeLayout directly is needed. - [Fact] - public void Validation_Does_Not_Throw_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null () - { - Application.Init (DriverRegistry.Names.ANSI); - - Runnable t = new Runnable (); - - var w = new Window { X = 1, Y = 2, Width = 3, Height = 5 }; - t.Add (w); - - t.IsModalChanged += (s, e) => - { - Assert.Equal (2, w.X = 2); - Assert.Equal (2, w.Y = 2); - }; - - Application.StopAfterFirstIteration = true; - - Application.Run (t); - t.Dispose (); - Application.Shutdown (); - } -} diff --git a/Tests/UnitTests/View/Layout/Pos.ViewTests.cs b/Tests/UnitTests/View/Layout/Pos.ViewTests.cs deleted file mode 100644 index 43b66d7542..0000000000 --- a/Tests/UnitTests/View/Layout/Pos.ViewTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -#nullable enable - -namespace UnitTests.LayoutTests; - -public class PosViewTests (ITestOutputHelper output) -{ - private readonly ITestOutputHelper _output = output; - - // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved - // TODO: A new test that calls SetRelativeLayout directly is needed. - [Fact] - [SetupFakeApplication] - public void Subtract_Operator () - { - var top = new Runnable (); - - var view = new View { X = 0, Y = 0, Width = 20, Height = 20 }; - var field = new TextField { X = 0, Y = 0, Width = 20 }; - var count = 20; - List listViews = new (); - - for (var i = 0; i < count; i++) - { - field.Text = $"View {i}"; - var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text }; - view.Add (view2); - Assert.Equal ($"View {i}", view2.Text); - Assert.Equal ($"Absolute({i})", field.Y.ToString ()); - listViews.Add (view2); - - Assert.Equal ($"Absolute({i})", field.Y.ToString ()); - field.Y += 1; - Assert.Equal ($"Absolute({i + 1})", field.Y.ToString ()); - } - - field.KeyDown += (s, k) => - { - if (k.KeyCode == KeyCode.Enter) - { - Assert.Equal ($"View {count - 1}", listViews [count - 1].Text); - view.Remove (listViews [count - 1]); - listViews [count - 1].Dispose (); - - Assert.Equal ($"Absolute({count})", field.Y.ToString ()); - field.Y -= 1; - count--; - Assert.Equal ($"Absolute({count})", field.Y.ToString ()); - } - }; - - Application.Iteration += OnApplicationOnIteration; - - var win = new Window (); - win.Add (view); - win.Add (field); - - top.Add (win); - - Application.Run (top); - Application.Iteration -= OnApplicationOnIteration; - - top.Dispose (); - Assert.Equal (0, count); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - - return; - - void OnApplicationOnIteration (object? s, EventArgs a) - { - while (count > 0) - { - field.NewKeyDownEvent (new (KeyCode.Enter)); - } - - Application.RequestStop (); - } - } -} diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs deleted file mode 100644 index 6d95ff37e7..0000000000 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ /dev/null @@ -1,630 +0,0 @@ -using Timeout = Terminal.Gui.App.Timeout; - -namespace UnitTests.ViewBaseTests.MouseTests; - -[Trait ("Category", "Input")] -public class MouseTests : TestsAllViews -{ - // TODO: Add more tests that ensure the above test works with positive adornments - - // Test drag to move - [Theory] - [InlineData (0, 0, 0, 0, false)] - [InlineData (0, 0, 0, 4, false)] - [InlineData (1, 0, 0, 4, false)] - [InlineData (0, 1, 0, 4, true)] - [InlineData (0, 0, 1, 4, false)] - [InlineData (1, 1, 0, 3, false)] - [InlineData (1, 1, 0, 4, false)] - [InlineData (1, 1, 0, 5, true)] - [InlineData (1, 1, 0, 6, false)] - [InlineData (1, 1, 0, 11, false)] - [InlineData (1, 1, 0, 12, true)] - [InlineData (1, 1, 0, 13, false)] - [InlineData (1, 1, 0, 14, false)] - [AutoInitShutdown] - public void ButtonPressed_In_Border_Starts_Drag (int marginThickness, int borderThickness, int paddingThickness, int xy, bool expectedMoved) - { - View testView = new () - { - CanFocus = true, - X = 4, - Y = 4, - Width = 10, - Height = 10, - Arrangement = ViewArrangement.Movable - }; - testView.Border.LineStyle = LineStyle.None; // Calls GetOrCreateView - testView.Margin.Thickness = new (marginThickness); - testView.Border.Thickness = new (borderThickness); - testView.Padding.Thickness = new (paddingThickness); - - Runnable top = new (); - top.Add (testView); - - SessionToken rs = Application.Begin (top); - Assert.Equal (4, testView.Frame.X); - - Assert.Equal (new (4, 4), testView.Frame.Location); - Application.RaiseMouseEvent (new () { ScreenPosition = new (xy, xy), Flags = MouseFlags.LeftButtonPressed }); - - Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.LeftButtonPressed | MouseFlags.PositionReport }); - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location); - // The above grabbed the mouse. Need to ungrab. - Application.Mouse.UngrabMouse (); - - top.Dispose (); - } - - [Theory] - [InlineData (MouseFlags.LeftButtonPressed, MouseFlags.LeftButtonReleased, MouseFlags.LeftButtonClicked)] - [InlineData (MouseFlags.MiddleButtonPressed, MouseFlags.MiddleButtonReleased, MouseFlags.MiddleButtonClicked)] - [InlineData (MouseFlags.RightButtonPressed, MouseFlags.RightButtonReleased, MouseFlags.RightButtonClicked)] - [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)] - public void MouseHoldRepeat_False_Button_Press_Release_DoesNotClick (MouseFlags pressed, MouseFlags released, MouseFlags clicked) - { - Mouse me = new (); - - View view = new () - { - Width = 1, - Height = 1, - MouseHoldRepeat = null - }; - - var clickedCount = 0; - - view.MouseEvent += (s, e) => clickedCount += e.IsSingleDoubleOrTripleClicked ? 1 : 0; - - me.Flags = pressed; - view.NewMouseEvent (me); - Assert.Equal (0, clickedCount); - me.Handled = false; - - me.Flags = pressed; - view.NewMouseEvent (me); - Assert.Equal (0, clickedCount); - me.Handled = false; - - me.Flags = released; - view.NewMouseEvent (me); - Assert.Equal (0, clickedCount); - me.Handled = false; - - me.Flags = clicked; - view.NewMouseEvent (me); - Assert.Equal (1, clickedCount); - - view.Dispose (); - - // LeftButtonPressed, LeftButtonReleased cause Application.Mouse.IsGrabbed to be set - Application.ResetState (true); - } - - [Theory] - [InlineData (MouseFlags.LeftButtonPressed, MouseFlags.LeftButtonReleased)] - [InlineData (MouseFlags.MiddleButtonPressed, MouseFlags.MiddleButtonReleased)] - [InlineData (MouseFlags.RightButtonPressed, MouseFlags.RightButtonReleased)] - [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)] - public void MouseHoldRepeat_True_And_MousePositionTracking_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released) - { - Mouse me = new (); - - View view = new () - { - Width = 1, - Height = 1, - MouseHoldRepeat = MouseFlags.LeftButtonReleased, - MousePositionTracking = true - }; - - // Setup components for mouse held down - TimedEvents timed = new (); - ApplicationMouse grab = new (); - view.MouseHoldRepeater = new MouseHoldRepeaterImpl (view, timed, grab); - - // Register callback for what to do when the mouse is held down - var clickedCount = 0; - view.MouseHoldRepeater.MouseIsHeldDownTick += (_, _) => clickedCount++; - - // Mouse is currently not held down so should be no timers running - Assert.Empty (timed.Timeouts); - - // When mouse is held down - me.Flags = pressed; - view.NewMouseEvent (me); - Assert.Equal (0, clickedCount); - me.Handled = false; - - // A timer should begin - KeyValuePair t = Assert.Single (timed.Timeouts); - - // Invoke the timer - t.Value.Callback.Invoke (); - - // Event should have been raised - Assert.Equal (1, clickedCount); - Assert.NotEmpty (timed.Timeouts); - - // When mouse is released - me.Flags = released; - view.NewMouseEvent (me); - - // timer should stop - Assert.Empty (timed.Timeouts); - Assert.Equal (1, clickedCount); - - view.Dispose (); - } - - [Theory] - [InlineData (MouseFlags.LeftButtonPressed, MouseFlags.LeftButtonReleased, MouseFlags.LeftButtonClicked)] - [InlineData (MouseFlags.MiddleButtonPressed, MouseFlags.MiddleButtonReleased, MouseFlags.MiddleButtonClicked)] - [InlineData (MouseFlags.RightButtonPressed, MouseFlags.RightButtonReleased, MouseFlags.RightButtonClicked)] - [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)] - public void MouseHoldRepeat_True_And_MousePositionTracking_True_Button_Press_Release_Clicks_Repeatedly ( - MouseFlags pressed, - MouseFlags released, - MouseFlags clicked - ) - { - Mouse me = new (); - - View view = new () - { - Width = 1, - Height = 1, - MouseHoldRepeat = MouseFlags.LeftButtonReleased, - MousePositionTracking = true - }; - - // Setup components for mouse held down - TimedEvents timed = new (); - ApplicationMouse grab = new (); - view.MouseHoldRepeater = new MouseHoldRepeaterImpl (view, timed, grab); - - // Register callback for what to do when the mouse is held down - var clickedCount = 0; - view.MouseHoldRepeater.MouseIsHeldDownTick += (_, _) => clickedCount++; - - Assert.Empty (timed.Timeouts); - - me.Flags = pressed; - view.NewMouseEvent (me); - Assert.Equal (0, clickedCount); - me.Handled = false; - - Assert.NotEmpty (timed.Timeouts); - Assert.Single (timed.Timeouts).Value.Callback.Invoke (); - - me.Flags = pressed; - view.NewMouseEvent (me); - Assert.Equal (1, clickedCount); - me.Handled = false; - - Assert.NotEmpty (timed.Timeouts); - - me.Flags = released; - view.NewMouseEvent (me); - Assert.Equal (1, clickedCount); - me.Handled = false; - - Assert.Empty (timed.Timeouts); - - me.Flags = clicked; - view.NewMouseEvent (me); - Assert.Equal (1, clickedCount); - - view.Dispose (); - } - - [Fact] - public void MouseHoldRepeat_True_And_MousePositionTracking_True_Move_InViewport_OutOfViewport_Keeps_Counting () - { - Mouse mouse = new () - { - Position = Point.Empty - }; - - View view = new () - { - Width = 1, - Height = 1, - MouseHoldRepeat = MouseFlags.LeftButtonReleased, - MousePositionTracking = true - }; - - // Setup components for mouse held down - TimedEvents timed = new (); - ApplicationMouse grab = new (); - view.MouseHoldRepeater = new MouseHoldRepeaterImpl (view, timed, grab); - - // Register callback for what to do when the mouse is held down - var clickedCount = 0; - view.MouseHoldRepeater.MouseIsHeldDownTick += (_, _) => clickedCount++; - - // Start in Viewport - mouse.Flags = MouseFlags.LeftButtonPressed; - mouse.Position = mouse.Position!.Value with { X = 0 }; - view.NewMouseEvent (mouse); - Assert.Equal (0, clickedCount); - mouse.Handled = false; - - // Mouse is held down so timer should be ticking - Assert.NotEmpty (timed.Timeouts); - Assert.Equal (0, clickedCount); - - // Don't wait, just force it to expire - Assert.Single (timed.Timeouts).Value.Callback.Invoke (); - Assert.Equal (1, clickedCount); - - // Move out of Viewport - mouse.Flags = MouseFlags.LeftButtonPressed; - mouse.Position = mouse.Position!.Value with { X = 1 }; - view.NewMouseEvent (mouse); - - Assert.Single (timed.Timeouts).Value.Callback.Invoke (); - Assert.Equal (2, clickedCount); - - mouse.Handled = false; - - // Move into Viewport - mouse.Flags = MouseFlags.LeftButtonPressed; - mouse.Position = mouse.Position!.Value with { X = 0 }; - view.NewMouseEvent (mouse); - - Assert.NotEmpty (timed.Timeouts); - Assert.Equal (2, clickedCount); - mouse.Handled = false; - - // Stay in Viewport - mouse.Flags = MouseFlags.LeftButtonPressed; - mouse.Position = mouse.Position!.Value with { X = 0 }; - view.NewMouseEvent (mouse); - - Assert.Single (timed.Timeouts).Value.Callback.Invoke (); - - Assert.Equal (3, clickedCount); - mouse.Handled = false; - - view.Dispose (); - } - - //[Theory] - //[InlineData (true, MouseState.None, 0, 0, 0, 0)] - //[InlineData (true, MouseState.In, 0, 0, 0, 0)] - //[InlineData (true, MouseState.Pressed, 0, 0, 1, 0)] - //[InlineData (true, MouseState.PressedOutside, 0, 0, 0, 1)] - //public void MouseState_LeftButton_Pressed_Then_Released_Outside (bool inViewport, MouseState highlightFlags, int noneCount, int expectedInCount, int expectedPressedCount, int expectedPressedOutsideCount) - //{ - // MouseEventTestView testView = new () - // { - // MouseHighlightStates = highlightFlags - // }; - - // Assert.Equal (0, testView.MouseStateInCount); - // Assert.Equal (0, testView.MouseStatePressedCount); - // Assert.Equal (0, testView.MouseStatePressedOutsideCount); - // Assert.Equal (0, testView.MouseStateNoneCount); - - // testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed, Position = new (inViewport ? 0 : 1, 0) }); - // Assert.Equal (expectedInCount, testView.MouseStateInCount); - // Assert.Equal (expectedPressedCount, testView.MouseStatePressedCount); - // Assert.Equal (expectedPressedOutsideCount, testView.MouseStatePressedOutsideCount); - // Assert.Equal (noneCount, testView.MouseStateNoneCount); - - // testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonReleased, Position = new (inViewport ? 0 : 1, 0) }); - // Assert.Equal (expectedInCount, testView.MouseStateInCount); - // Assert.Equal (expectedPressedCount, testView.MouseStatePressedCount); - // Assert.Equal (expectedPressedOutsideCount, testView.MouseStatePressedOutsideCount); - // Assert.Equal (noneCount, testView.MouseStateNoneCount); - - // testView.Dispose (); - - // // LeftButtonPressed, LeftButtonReleased cause Application.Mouse.IsGrabbed to be set - // Application.ResetState (true); - - //} - - // TODO: Add tests for each combination of HighlightFlags - - [Theory] - [InlineData (0)] - [InlineData (1)] - [InlineData (10)] - public void MouseState_None_LeftButton_Pressed_Move_No_Changes (int x) - { - MouseEventTestView testView = new () - { - MouseHighlightStates = MouseState.None - }; - - bool inViewport = testView.Viewport.Contains (x, 0); - - // Start at 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - - // Move to x,0 - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed, Position = new (x, 0) }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - - // Move back to 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - - testView.Dispose (); - - // LeftButtonPressed, LeftButtonReleased cause Application.Mouse.IsGrabbed to be set - Application.ResetState (true); - } - - [Theory] - [InlineData (0)] - [InlineData (1)] - [InlineData (10)] - public void MouseState_Pressed_LeftButton_Pressed_Move_Keeps_Pressed (int x) - { - MouseEventTestView testView = new () - { - MouseHighlightStates = MouseState.Pressed - }; - - bool inViewport = testView.Viewport.Contains (x, 0); - - // Start at 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (1, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - - // Move to x,0 - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed, Position = new (x, 0) }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (1, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (1, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - - // Move backto 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (1, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (1, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - - testView.Dispose (); - - // LeftButtonPressed, LeftButtonReleased cause Application.Mouse.IsGrabbed to be set - Application.ResetState (true); - } - - [Theory] - [InlineData (0)] - [InlineData (1)] - [InlineData (10)] - public void MouseState_PressedOutside_LeftButton_Pressed_Move_Raises_PressedOutside (int x) - { - MouseEventTestView testView = new () - { - MouseHighlightStates = MouseState.PressedOutside, - MouseHoldRepeat = null - }; - - bool inViewport = testView.Viewport.Contains (x, 0); - - // Start at 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - - // Move to x,0 - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed, Position = new (x, 0) }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (1, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - - // Move backto 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (1, testView.MouseStatePressedOutsideCount); - Assert.Equal (1, testView.MouseStateNoneCount); - } - - testView.Dispose (); - - // LeftButtonPressed, LeftButtonReleased cause Application.Mouse.IsGrabbed to be set - Application.ResetState (true); - } - - [Theory] - [InlineData (0)] - [InlineData (1)] - [InlineData (10)] - public void MouseState_PressedOutside_LeftButton_Pressed_Move_Raises_PressedOutside_MouseHoldRepeat (int x) - { - MouseEventTestView testView = new () - { - MouseHighlightStates = MouseState.PressedOutside, - MouseHoldRepeat = MouseFlags.LeftButtonReleased - }; - - bool inViewport = testView.Viewport.Contains (x, 0); - - // Start at 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - - // Move to x,0 - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed, Position = new (x, 0) }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - - // Move backto 0,0 ; in viewport - testView.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonPressed }); - - if (inViewport) - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - else - { - Assert.Equal (0, testView.MouseStateInCount); - Assert.Equal (0, testView.MouseStatePressedCount); - Assert.Equal (0, testView.MouseStatePressedOutsideCount); - Assert.Equal (0, testView.MouseStateNoneCount); - } - - testView.Dispose (); - - // LeftButtonPressed, LeftButtonReleased cause Application.Mouse.IsGrabbed to be set - Application.ResetState (true); - } - - private class MouseEventTestView : View - { - public int MouseEnterCount { get; private set; } - public int MouseLeaveCount { get; private set; } - public int MouseStatePressedOutsideCount { get; private set; } - public int MouseStateInCount { get; private set; } - public int MouseStatePressedCount { get; private set; } - public int MouseStateNoneCount { get; private set; } - - public MouseEventTestView () - { - Height = 1; - Width = 1; - CanFocus = true; - Id = "mouseEventTestView"; - - MouseLeave += (s, e) => { MouseEnterCount++; }; - MouseEnter += (s, e) => { MouseLeaveCount++; }; - } - - /// - protected override void OnMouseStateChanged (EventArgs args) - { - switch (args.Value) - { - case MouseState.None: - MouseStateNoneCount++; - - break; - case MouseState.In: - MouseStateInCount++; - - break; - - case MouseState.Pressed: - MouseStatePressedCount++; - - break; - - case MouseState.PressedOutside: - MouseStatePressedOutsideCount++; - - break; - } - - base.OnMouseStateChanged (args); - } - } -} diff --git a/Tests/UnitTests/View/Navigation/EnabledTests.cs b/Tests/UnitTests/View/Navigation/EnabledTests.cs deleted file mode 100644 index 6524b196cd..0000000000 --- a/Tests/UnitTests/View/Navigation/EnabledTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -#nullable enable -namespace UnitTests.ViewBaseTests; - -public class EnabledTests -{ - - [Fact] - [AutoInitShutdown] - public void _Enabled_Sets_Also_Sets_SubViews () - { - var wasClicked = false; - var button = new Button { Text = "Click Me" }; - button.IsDefault = true; - button.Accepting += (s, e) => wasClicked = !wasClicked; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (button); - var top = new Runnable (); - top.Add (win); - - var iterations = 0; - - Application.Iteration += OnApplicationOnIteration; - - Application.Run (top); - Application.Iteration -= OnApplicationOnIteration; - - Assert.Equal (1, iterations); - top.Dispose (); - - return; - - void OnApplicationOnIteration (object? s, EventArgs a) - { - iterations++; - - win.NewKeyDownEvent (Key.Enter); - Assert.True (wasClicked); - button.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonClicked }); - Assert.False (wasClicked); - Assert.True (button.Enabled); - Assert.True (button.CanFocus); - Assert.True (button.HasFocus); - Assert.True (win.Enabled); - Assert.True (win.CanFocus); - Assert.True (win.HasFocus); - - Assert.True (button.HasFocus); - win.Enabled = false; - Assert.False (button.HasFocus); - button.NewKeyDownEvent (Key.Enter); - Assert.False (wasClicked); - button.NewMouseEvent (new () { Flags = MouseFlags.LeftButtonClicked }); - Assert.False (wasClicked); - Assert.False (button.Enabled); - Assert.True (button.CanFocus); - Assert.False (button.HasFocus); - Assert.False (win.Enabled); - Assert.True (win.CanFocus); - Assert.False (win.HasFocus); - button.SetFocus (); - Assert.False (button.HasFocus); - Assert.False (win.HasFocus); - win.SetFocus (); - Assert.False (button.HasFocus); - Assert.False (win.HasFocus); - - win.Enabled = true; - win.FocusDeepest (NavigationDirection.Forward, null); - Assert.True (button.HasFocus); - Assert.True (win.HasFocus); - - Application.RequestStop (); - } - } -} diff --git a/Tests/UnitTests/View/SchemeTests.cs b/Tests/UnitTests/View/SchemeTests.cs deleted file mode 100644 index 9660a853b6..0000000000 --- a/Tests/UnitTests/View/SchemeTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Xunit; - -namespace UnitTests.ViewBaseTests; - -[Trait ("Category", "View.Scheme")] -public class SchemeTests -{ - [Fact] - [UnitTests.AutoInitShutdown] - public void View_Resolves_Attributes_From_Scheme () - { - View view = new Label { SchemeName = "Base" }; - - foreach (VisualRole role in Enum.GetValues ()) - { - Attribute attr = view.GetAttributeForRole (role); - Assert.NotEqual (default, attr.Foreground); // Defensive: avoid all-defaults - } - - view.Dispose (); - } -} diff --git a/Tests/UnitTests/View/SubviewTests.cs b/Tests/UnitTests/View/SubviewTests.cs deleted file mode 100644 index 42f31baacb..0000000000 --- a/Tests/UnitTests/View/SubviewTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -namespace UnitTests.ViewBaseTests; - -public class SubViewTests -{ - //private readonly ITestOutputHelper _output; - //public SubViewTests (ITestOutputHelper output) { _output = output; } - - //// TODO: This is a poor unit tests. Not clear what it's testing. Refactor. - //[Fact] - //[AutoInitShutdown] - //public void Initialized_Event_Will_Be_Invoked_When_Added_Dynamically () - //{ - // var t = new Runnable { Id = "0" }; - - // var w = new Window { Id = "t", Width = Dim.Fill (), Height = Dim.Fill () }; - // var v1 = new View { Id = "v1", Width = Dim.Fill (), Height = Dim.Fill () }; - // var v2 = new View { Id = "v2", Width = Dim.Fill (), Height = Dim.Fill () }; - - // int tc = 0, wc = 0, v1c = 0, v2c = 0, sv1c = 0; - - // t.Initialized += (s, e) => - // { - // tc++; - // Assert.Equal (1, tc); - // Assert.Equal (1, wc); - // Assert.Equal (1, v1c); - // Assert.Equal (1, v2c); - // Assert.Equal (0, sv1c); // Added after t in the Application.Iteration. - - // Assert.True (t.CanFocus); - // Assert.True (w.CanFocus); - // Assert.False (v1.CanFocus); - // Assert.False (v2.CanFocus); - - // Application.LayoutAndDraw (); - // }; - - // w.Initialized += (s, e) => - // { - // wc++; - // Assert.Equal (t.Viewport.Width, w.Frame.Width); - // Assert.Equal (t.Viewport.Height, w.Frame.Height); - // }; - - // v1.Initialized += (s, e) => - // { - // v1c++; - - // //Assert.Equal (t.Viewport.Width, v1.Frame.Width); - // //Assert.Equal (t.Viewport.Height, v1.Frame.Height); - // }; - - // v2.Initialized += (s, e) => - // { - // v2c++; - - // //Assert.Equal (t.Viewport.Width, v2.Frame.Width); - // //Assert.Equal (t.Viewport.Height, v2.Frame.Height); - // }; - // w.Add (v1, v2); - // t.Add (w); - - // Application.Iteration += OnApplicationOnIteration; - - // Application.Run (t); - // Application.Iteration -= OnApplicationOnIteration; - - // t.Dispose (); - // Application.Shutdown (); - - // Assert.Equal (1, tc); - // Assert.Equal (1, wc); - // Assert.Equal (1, v1c); - // Assert.Equal (1, v2c); - // Assert.Equal (1, sv1c); - - // Assert.True (t.CanFocus); - // Assert.True (w.CanFocus); - // Assert.False (v1.CanFocus); - // Assert.False (v2.CanFocus); - - // return; - - // void OnApplicationOnIteration (object s, EventArgs a) - // { - // var sv1 = new View { Id = "sv1", Width = Dim.Fill (), Height = Dim.Fill () }; - - // sv1.Initialized += (s, e) => - // { - // sv1c++; - // Assert.NotEqual (t.Frame.Width, sv1.Frame.Width); - // Assert.NotEqual (t.Frame.Height, sv1.Frame.Height); - // Assert.False (sv1.CanFocus); - - // //Assert.Throws (() => sv1.CanFocus = true); - // Assert.False (sv1.CanFocus); - // }; - - // v1.Add (sv1); - - // Application.LayoutAndDraw (); - // t.Running = false; - // } - //} -} diff --git a/Tests/UnitTests/View/TextTests.cs b/Tests/UnitTests/View/TextTests.cs deleted file mode 100644 index 5af1fa3804..0000000000 --- a/Tests/UnitTests/View/TextTests.cs +++ /dev/null @@ -1,1142 +0,0 @@ -using System.Text; - -namespace UnitTests.ViewBaseTests; - -/// -/// Tests of the and properties. -/// -public class TextTests (ITestOutputHelper output) -{ - [Fact] - [SetupFakeApplication] - public void Setting_With_Height_Horizontal () - { - var top = new View { Width = 25, Height = 25 }; - top.App = ApplicationImpl.Instance; - - var label = new Label { Text = "Hello", /* Width = 10, Height = 2, */ ValidatePosDim = true }; - var viewX = new View { Text = "X", X = Pos.Right (label), Width = 1, Height = 1 }; - var viewY = new View { Text = "Y", Y = Pos.Bottom (label), Width = 1, Height = 1 }; - - top.Add (label, viewX, viewY); - top.Layout (); - - Assert.Equal (new (0, 0, 5, 1), label.Frame); - - top.LayoutSubViews (); - top.Draw (); - - var expected = @" -HelloX -Y -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - label.Width = 10; - label.Height = 2; - - Assert.Equal (new (0, 0, 10, 2), label.Frame); - - top.LayoutSubViews (); - top.SetClipToScreen (); - top.Draw (); - - expected = @" -Hello X - -Y -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - } - - [Fact] - [AutoInitShutdown] - public void Setting_With_Height_Vertical () - { - // BUGBUG: Label is Width = Dim.Auto (), Height = Dim.Auto (), so Width & Height are ignored - var label = new Label - { /*Width = 2, Height = 10, */ - TextDirection = TextDirection.TopBottom_LeftRight, ValidatePosDim = true - }; - var viewX = new View { Text = "X", X = Pos.Right (label), Width = 1, Height = 1 }; - var viewY = new View { Text = "Y", Y = Pos.Bottom (label), Width = 1, Height = 1 }; - - var top = new Runnable (); - top.Add (label, viewX, viewY); - SessionToken rs = Application.Begin (top); - - label.Text = "Hello"; - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 1, 5), label.Frame); - - var expected = @" -HX -e -l -l -o -Y -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - label.Width = 2; - label.Height = 10; - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 2, 10), label.Frame); - - expected = @" -H X -e -l -l -o - - - - - -Y -" - ; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TextDirection_Toggle () - { - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - - var view = new View (); - win.Add (view); - var top = new Runnable (); - top.Add (win); - - SessionToken rs = Application.Begin (top); - Application.Driver!.SetScreenSize (15, 15); - Application.LayoutAndDraw (); - - Assert.Equal (new (0, 0, 15, 15), win.Frame); - Assert.Equal (new (0, 0, 15, 15), win.Margin.GetFrame ()); - Assert.Equal (new (0, 0, 15, 15), win.Border.GetFrame ()); - Assert.Equal (new (1, 1, 13, 13), win.Padding.GetFrame ()); - - Assert.Equal (TextDirection.LeftRight_TopBottom, view.TextDirection); - Assert.Equal (Rectangle.Empty, view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(0)", view.Width.ToString ()); - Assert.Equal ("Absolute(0)", view.Height.ToString ()); - - var expected = @" -┌─────────────┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└─────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - view.Text = "Hello World"; - view.Width = 11; - view.Height = 1; - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (new (0, 0, 11, 1), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(11)", view.Width.ToString ()); - Assert.Equal ("Absolute(1)", view.Height.ToString ()); - - expected = @" -┌─────────────┐ -│Hello World │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└─────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - view.Width = Dim.Auto (); - view.Height = Dim.Auto (); - view.Text = "Hello Worlds"; - AutoInitShutdownAttribute.RunIteration (); - int len = "Hello Worlds".Length; - Assert.Equal (12, len); - Assert.Equal (new (0, 0, len, 1), view.Frame); - - expected = @" -┌─────────────┐ -│Hello Worlds │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└─────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - view.TextDirection = TextDirection.TopBottom_LeftRight; - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (new (0, 0, 1, 12), view.Frame); - Assert.Equal (new (0, 0, 1, 12), view.Frame); - - expected = @" -┌─────────────┐ -│H │ -│e │ -│l │ -│l │ -│o │ -│ │ -│W │ -│o │ -│r │ -│l │ -│d │ -│s │ -│ │ -└─────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - // Setting to false causes Width and Height to be set to the current ContentSize - view.Width = 1; - view.Height = 12; - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (new (0, 0, 1, 12), view.Frame); - - view.Width = 12; - view.Height = 1; - view.TextFormatter.ConstrainToSize = new (12, 1); - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (new (12, 1), view.TextFormatter.ConstrainToSize); - Assert.Equal (new (0, 0, 12, 1), view.Frame); - - top.ClearViewport (); - view.SetNeedsDraw (); - view.Draw (); - expected = @" HelloWorlds"; - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - AutoInitShutdownAttribute.RunIteration (); - - // TextDirection.TopBottom_LeftRight - Height of 1 and Width of 12 means - // that the text will be spread "vertically" across 1 line. - // Hence no space. - expected = @" -┌─────────────┐ -│HelloWorlds │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└─────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - view.PreserveTrailingSpaces = true; - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 12, 1), view.Frame); - - expected = @" -┌─────────────┐ -│Hello Worlds │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└─────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - view.PreserveTrailingSpaces = false; - Rectangle f = view.Frame; - view.Width = f.Height; - view.Height = f.Width; - view.TextDirection = TextDirection.TopBottom_LeftRight; - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 1, 12), view.Frame); - - expected = @" -┌─────────────┐ -│H │ -│e │ -│l │ -│l │ -│o │ -│ │ -│W │ -│o │ -│r │ -│l │ -│d │ -│s │ -│ │ -└─────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - view.Width = Dim.Auto (); - view.Height = Dim.Auto (); - - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 1, 12), view.Frame); - - expected = @" -┌─────────────┐ -│H │ -│e │ -│l │ -│l │ -│o │ -│ │ -│W │ -│o │ -│r │ -│l │ -│d │ -│s │ -│ │ -└─────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void View_IsEmpty_False_Minimum_Width () - { - var text = "Views"; - - var view = new View - { - TextDirection = TextDirection.TopBottom_LeftRight, - Height = Dim.Fill () - text.Length, - Text = text - }; - view.Width = Dim.Auto (); - view.Height = Dim.Auto (); - - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (view); - var top = new Runnable (); - top.Add (win); - SessionToken rs = Application.Begin (top); - Application.Driver!.SetScreenSize (4, 10); - Application.LayoutAndDraw (); - - Assert.Equal (5, text.Length); - - Assert.Equal (new (0, 0, 1, 5), view.Frame); - Assert.Equal (new (1, 5), view.TextFormatter.ConstrainToSize); - Assert.Equal (new () { "Views" }, view.TextFormatter.GetLines ()); - Assert.Equal (new (0, 0, 4, 10), win.Frame); - Assert.Equal (new (0, 0, 4, 10), Application.TopRunnableView.Frame); - - var expected = @" -┌──┐ -│V │ -│i │ -│e │ -│w │ -│s │ -│ │ -│ │ -│ │ -└──┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 4, 10), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - - //view.Height = Dim.Fill () - text.Length; - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 1, 5), view.Frame); - Assert.Equal (new (1, 5), view.TextFormatter.ConstrainToSize); - Exception exception = Record.Exception (() => Assert.Single (view.TextFormatter.GetLines ())); - Assert.Null (exception); - - expected = @" -┌──┐ -│V │ -│i │ -│e │ -│w │ -│s │ -│ │ -│ │ -│ │ -└──┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 4, 10), pos); - top.Dispose (); - } - - [Fact] - [SetupFakeApplication] - public void DimAuto_Vertical_TextDirection_Wide_Rune () - { - var text = "界View"; - - var view = new View - { - App = ApplicationImpl.Instance, - TextDirection = TextDirection.TopBottom_LeftRight, - Text = text, - Width = Dim.Auto (), - Height = Dim.Auto () - }; - - view.SetRelativeLayout (new (4, 10)); - - Assert.Equal (5, text.Length); - - // Vertical text - 2 wide, 5 down - Assert.Equal (new (0, 0, 2, 5), view.Frame); - Assert.Equal (new (2, 5), view.TextFormatter.ConstrainToSize); - Assert.Equal (new () { "界View" }, view.TextFormatter.GetLines ()); - - view.Draw (); - - var expected = @" -界 -V -i -e -w "; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - } - - [Fact] - [AutoInitShutdown] - public void Width_Height_SetMinWidthHeight_Narrow_Wide_Runes () - { - var text = $"0123456789{Environment.NewLine}01234567891"; - - var horizontalView = new View - { - Width = Dim.Auto (), - Height = Dim.Auto (), - Text = text - }; - - var verticalView = new View - { - Width = Dim.Auto (), - Height = Dim.Auto (), - Y = 3, - - //Height = 11, - //Width = 2, - Text = text, - TextDirection = TextDirection.TopBottom_LeftRight - }; - - var win = new Window - { - Width = Dim.Fill (), - Height = Dim.Fill (), - Text = "Window" - }; - win.Add (horizontalView, verticalView); - var top = new Runnable (); - top.Add (win); - SessionToken rs = Application.Begin (top); - Application.Driver!.SetScreenSize (20, 20); - Application.LayoutAndDraw (); - - Assert.Equal (new (0, 0, 11, 2), horizontalView.Frame); - Assert.Equal (new (0, 3, 2, 11), verticalView.Frame); - - var expected = @" -┌──────────────────┐ -│0123456789 │ -│01234567891 │ -│ │ -│00 │ -│11 │ -│22 │ -│33 │ -│44 │ -│55 │ -│66 │ -│77 │ -│88 │ -│99 │ -│ 1 │ -│ │ -│ │ -│ │ -│ │ -└──────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - verticalView.Text = $"最初の行{Environment.NewLine}二行目"; - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (new (0, 3, 4, 4), verticalView.Frame); - - expected = @" -┌──────────────────┐ -│0123456789 │ -│01234567891 │ -│ │ -│最二 │ -│初行 │ -│の目 │ -│行 │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──────────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Width_Height_Stay_True_If_TextFormatter_Size_Fit () - { - var text = "Finish 終"; - - var horizontalView = new View - { - Id = "horizontalView", - Width = Dim.Auto (), Height = Dim.Auto (), Text = text - }; - - var verticalView = new View - { - Id = "verticalView", - Y = 3, - Width = Dim.Auto (), - Height = Dim.Auto (), - Text = text, - TextDirection = TextDirection.TopBottom_LeftRight - }; - var win = new Window { Id = "win", Width = Dim.Fill (), Height = Dim.Fill (), Text = "Window" }; - win.Add (horizontalView, verticalView); - var top = new Runnable (); - top.Add (win); - SessionToken rs = Application.Begin (top); - Application.Driver!.SetScreenSize (22, 22); - Application.LayoutAndDraw (); - - Assert.Equal (new (text.GetColumns (), 1), horizontalView.TextFormatter.ConstrainToSize); - Assert.Equal (new (2, 8), verticalView.TextFormatter.ConstrainToSize); - - //Assert.Equal (new (0, 0, 10, 1), horizontalView.Frame); - //Assert.Equal (new (0, 3, 10, 9), verticalView.Frame); - - var expected = @" -┌────────────────────┐ -│Finish 終 │ -│ │ -│ │ -│F │ -│i │ -│n │ -│i │ -│s │ -│h │ -│ │ -│終 │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - verticalView.Text = "最初の行二行目"; - AutoInitShutdownAttribute.RunIteration (); - - // height was initialized with 8 and can only grow or keep initial value - Assert.Equal (new (0, 3, 2, 7), verticalView.Frame); - - expected = @" -┌────────────────────┐ -│Finish 終 │ -│ │ -│ │ -│最 │ -│初 │ -│の │ -│行 │ -│二 │ -│行 │ -│目 │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Excess_Text_Is_Erased_When_The_Width_Is_Reduced () - { - var lbl = new Label { Text = "123" }; - var top = new Runnable (); - top.Add (lbl); - SessionToken rs = Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 3, 1), lbl.Frame); - - Assert.Equal ("123 ", GetContents ()); - - lbl.Text = "12"; - lbl.Layout (); - - Assert.Equal (new (0, 0, 2, 1), lbl.Frame); - Assert.Equal (new (0, 0, 2, 1), lbl.NeedsDrawRect); - Assert.Equal (new (0, 0, 80, 25), lbl.SuperView.NeedsDrawRect); - Assert.True (lbl.SuperView.NeedsLayout); - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal ("12 ", GetContents ()); - - string GetContents () - { - var sb = new StringBuilder (); - - for (var i = 0; i < 4; i++) - { - sb.Append (Application.Driver?.Contents! [0, i].Grapheme); - } - - return sb.ToString (); - } - - Application.End (rs); - top.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (true)] - [InlineData (false)] - public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) - { - var text = "Hello World"; - var width = 20; - - var lblLeft = new View - { - Text = text, - Width = width, - Height = 1 - }; - - if (autoSize) - { - lblLeft.Width = Dim.Auto (); - lblLeft.Height = Dim.Auto (); - } - - var lblCenter = new View - { - Text = text, - Y = 1, - Width = width, - Height = 1, - TextAlignment = Alignment.Center - }; - - if (autoSize) - { - lblCenter.Width = Dim.Auto (); - lblCenter.Height = Dim.Auto (); - } - - var lblRight = new View - { - Text = text, - Y = 2, - Width = width, - Height = 1, - TextAlignment = Alignment.End - }; - - if (autoSize) - { - lblRight.Width = Dim.Auto (); - lblRight.Height = Dim.Auto (); - } - - var lblJust = new View - { - Text = text, - Y = 3, - Width = width, - Height = 1, - TextAlignment = Alignment.Fill - }; - - if (autoSize) - { - lblJust.Width = Dim.Auto (); - lblJust.Height = Dim.Auto (); - } - - var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single }; - frame.Add (lblLeft, lblCenter, lblRight, lblJust); - var top = new Runnable (); - top.Add (frame); - Application.Begin (top); - Application.Driver!.SetScreenSize (width + 2, 6); - Application.LayoutAndDraw (); - - // frame.Width is width + border wide (20 + 2) and 6 high - - if (autoSize) - { - Size expectedSize = new (11, 1); - Assert.Equal (expectedSize, lblLeft.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblCenter.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblRight.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblJust.TextFormatter.ConstrainToSize); - } - else - { - Size expectedSize = new (width, 1); - Assert.Equal (expectedSize, lblLeft.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblCenter.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblRight.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblJust.TextFormatter.ConstrainToSize); - } - - Assert.Equal (new (0, 0, width + 2, 6), frame.Frame); - - string expected; - - if (autoSize) - { - expected = @" -┌────────────────────┐ -│Hello World │ -│Hello World │ -│Hello World │ -│Hello World │ -└────────────────────┘ -"; - } - else - { - expected = @" -┌────────────────────┐ -│Hello World │ -│ Hello World │ -│ Hello World│ -│Hello World│ -└────────────────────┘ -"; - } - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, width + 2, 6), pos); - top.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (true)] - [InlineData (false)] - public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) - { - var text = "Hello World"; - var height = 20; - - var lblLeft = new View - { - Text = text, - Width = 1, - Height = height, - TextDirection = TextDirection.TopBottom_LeftRight - }; - - if (autoSize) - { - lblLeft.Width = Dim.Auto (); - lblLeft.Height = Dim.Auto (); - } - - var lblCenter = new View - { - Text = text, - X = 2, - Width = 1, - Height = height, - TextDirection = TextDirection.TopBottom_LeftRight, - VerticalTextAlignment = Alignment.Center - }; - - if (autoSize) - { - lblCenter.Width = Dim.Auto (); - lblCenter.Height = Dim.Auto (); - } - - var lblRight = new View - { - Text = text, - X = 4, - Width = 1, - Height = height, - TextDirection = TextDirection.TopBottom_LeftRight, - VerticalTextAlignment = Alignment.End - }; - - if (autoSize) - { - lblRight.Width = Dim.Auto (); - lblRight.Height = Dim.Auto (); - } - - var lblJust = new View - { - Text = text, - X = 6, - Width = 1, - Height = height, - TextDirection = TextDirection.TopBottom_LeftRight, - VerticalTextAlignment = Alignment.Fill - }; - - if (autoSize) - { - lblJust.Width = Dim.Auto (); - lblJust.Height = Dim.Auto (); - } - - var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single }; - - frame.Add (lblLeft, lblCenter, lblRight, lblJust); - var top = new Runnable (); - top.Add (frame); - Application.Begin (top); - Application.Driver!.SetScreenSize (9, height + 2); - Application.LayoutAndDraw (); - - if (autoSize) - { - Assert.Equal (new (1, 11), lblLeft.TextFormatter.ConstrainToSize); - Assert.Equal (new (1, 11), lblCenter.TextFormatter.ConstrainToSize); - Assert.Equal (new (1, 11), lblRight.TextFormatter.ConstrainToSize); - Assert.Equal (new (1, 11), lblJust.TextFormatter.ConstrainToSize); - Assert.Equal (new (0, 0, 9, height + 2), frame.Frame); - } - else - { - Assert.Equal (new (1, height), lblLeft.TextFormatter.ConstrainToSize); - Assert.Equal (new (1, height), lblCenter.TextFormatter.ConstrainToSize); - Assert.Equal (new (1, height), lblRight.TextFormatter.ConstrainToSize); - Assert.Equal (new (1, height), lblJust.TextFormatter.ConstrainToSize); - Assert.Equal (new (0, 0, 9, height + 2), frame.Frame); - } - - string expected; - - if (autoSize) - { - expected = @" -┌───────┐ -│H H H H│ -│e e e e│ -│l l l l│ -│l l l l│ -│o o o o│ -│ │ -│W W W W│ -│o o o o│ -│r r r r│ -│l l l l│ -│d d d d│ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└───────┘ -"; - } - else - { - expected = @" -┌───────┐ -│H H│ -│e e│ -│l l│ -│l l│ -│o H o│ -│ e │ -│W l │ -│o l │ -│r o │ -│l H │ -│d W e │ -│ o l │ -│ r l │ -│ l o │ -│ d │ -│ W W│ -│ o o│ -│ r r│ -│ l l│ -│ d d│ -└───────┘ -"; - } - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 9, height + 2), pos); - top.Dispose (); - } - - [Fact] - [SetupFakeApplication] - public void Narrow_Wide_Runes () - { - Application.Driver!.SetScreenSize (32, 32); - var top = new View { Width = 32, Height = 32 }; - top.App = ApplicationImpl.Instance; - - var text = $"First line{Environment.NewLine}Second line"; - var horizontalView = new View { Width = 20, Height = 1, Text = text }; - - // Autosize is off, so we have to explicitly set TextFormatter.Size - horizontalView.TextFormatter.ConstrainToSize = new (20, 1); - - var verticalView = new View - { - Y = 3, - Height = 20, - Width = 1, - Text = text, - TextDirection = TextDirection.TopBottom_LeftRight - }; - - // Autosize is off, so we have to explicitly set TextFormatter.Size - verticalView.TextFormatter.ConstrainToSize = new (1, 20); - - var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill (), Text = "Window", BorderStyle = LineStyle.Single }; - frame.Add (horizontalView, verticalView); - top.Add (frame); - top.BeginInit (); - top.EndInit (); - - Assert.Equal (new (0, 0, 20, 1), horizontalView.Frame); - Assert.Equal (new (0, 3, 1, 20), verticalView.Frame); - - top.Draw (); - - var expected = @" -┌──────────────────────────────┐ -│First line Second li │ -│ │ -│ │ -│F │ -│i │ -│r │ -│s │ -│t │ -│ │ -│l │ -│i │ -│n │ -│e │ -│ │ -│S │ -│e │ -│c │ -│o │ -│n │ -│d │ -│ │ -│l │ -│i │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──────────────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - verticalView.Text = $"最初の行{Environment.NewLine}二行目"; - Assert.True (verticalView.TextFormatter.NeedsFormat); - - // Autosize is off, so we have to explicitly set TextFormatter.Size - // We know these glpyhs are 2 cols wide, so we need to widen the view - verticalView.Width = 2; - verticalView.TextFormatter.ConstrainToSize = new (2, 20); - Assert.True (verticalView.TextFormatter.NeedsFormat); - top.SetClipToScreen (); - top.Draw (); - Assert.Equal (new (0, 3, 2, 20), verticalView.Frame); - - expected = @" -┌──────────────────────────────┐ -│First line Second li │ -│ │ -│ │ -│最 │ -│初 │ -│の │ -│行 │ -│ │ -│二 │ -│行 │ -│目 │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──────────────────────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - } - - [Fact] - [SetupFakeApplication] - public void SetText_RendersCorrectly () - { - View view; - var text = "test"; - - view = new Label { App = ApplicationImpl.Instance, Text = text }; - view.BeginInit (); - view.EndInit (); - view.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre (text, output); - } -} diff --git a/Tests/UnitTests/View/ViewTests.cs b/Tests/UnitTests/View/ViewTests.cs deleted file mode 100644 index 86b63fe421..0000000000 --- a/Tests/UnitTests/View/ViewTests.cs +++ /dev/null @@ -1,552 +0,0 @@ -namespace UnitTests.ViewBaseTests; - -public class ViewTests -{ - private readonly ITestOutputHelper _output; - - public ViewTests (ITestOutputHelper output) => _output = output; - -#if DEBUG_IDISPOSABLE - - // Generic lifetime (IDisposable) tests - [Fact] - [TestRespondersDisposed] - public void Dispose_Works () - { - var r = new View (); - - Assert.Single (View.Instances); - - r.Dispose (); - Assert.Empty (View.Instances); - } -#endif - - [Fact] - public void Disposing_Event_Notify_All_Subscribers_On_The_First_Container () - { -#if DEBUG_IDISPOSABLE - - // Only clear before because need to test after assert - View.Instances.Clear (); -#endif - - var container1 = new View { Id = "Container1" }; - var count = 0; - - var view = new View { Id = "View" }; - view.Disposing += ViewDisposing; - container1.Add (view); - Assert.Equal (container1, view.SuperView); - - Assert.Single (container1.SubViews); - - var container2 = new View { Id = "Container2" }; - - // BUGBUG: It's not legit to add a View to two SuperViews - container2.Add (view); - Assert.Equal (container2, view.SuperView); - Assert.Equal (container1.SubViews.Count, container2.SubViews.Count); - container2.Dispose (); - - Assert.Empty (container1.SubViews); - Assert.Empty (container2.SubViews); - Assert.Equal (1, count); - Assert.Null (view.SuperView); - - container1.Dispose (); - -#if DEBUG_IDISPOSABLE - Assert.Empty (View.Instances); -#endif - - return; - - void ViewDisposing (object sender, EventArgs e) - { - count++; - Assert.Equal (view, sender); - container1.Remove ((View)sender); - } - } - - [Fact] - public void Disposing_Event_Notify_All_Subscribers_On_The_Second_Container () - { -#if DEBUG_IDISPOSABLE - - // Only clear before because need to test after assert - View.Instances.Clear (); -#endif - - var container1 = new View { Id = "Container1" }; - - var view = new View { Id = "View" }; - container1.Add (view); - Assert.Equal (container1, view.SuperView); - Assert.Single (container1.SubViews); - - var container2 = new View { Id = "Container2" }; - var count = 0; - - view.Disposing += View_Disposing; - - // BUGBUG: It's not legit to add a View to two SuperViews - container2.Add (view); - Assert.Equal (container2, view.SuperView); - - void View_Disposing (object sender, EventArgs e) - { - count++; - Assert.Equal (view, sender); - container2.Remove ((View)sender); - } - - Assert.Equal (container1.SubViews.Count, container2.SubViews.Count); - container1.Dispose (); - - Assert.Empty (container1.SubViews); - Assert.Empty (container2.SubViews); - Assert.Equal (1, count); - Assert.Null (view.SuperView); - - container2.Dispose (); - -#if DEBUG_IDISPOSABLE - Assert.Empty (View.Instances); -#endif - } - - [Fact] - public void Not_Notifying_Dispose () - { - // Only clear before because need to test after assert -#if DEBUG_IDISPOSABLE - View.Instances.Clear (); -#endif - var container1 = new View { Id = "Container1" }; - - var view = new View { Id = "View" }; - container1.Add (view); - Assert.Equal (container1, view.SuperView); - - Assert.Single (container1.SubViews); - - var container2 = new View { Id = "Container2" }; - - // BUGBUG: It's not legit to add a View to two SuperViews - container2.Add (view); - Assert.Equal (container2, view.SuperView); - Assert.Equal (container1.SubViews.Count, container2.SubViews.Count); - container1.Dispose (); - - Assert.Empty (container1.SubViews); - Assert.NotEmpty (container2.SubViews); - Assert.Single (container2.SubViews); - Assert.Null (view.SuperView); - - // Trying access disposed properties -#if DEBUG_IDISPOSABLE - Assert.True (container2.SubViews.ElementAt (0).WasDisposed); -#endif - Assert.False (container2.SubViews.ElementAt (0).CanFocus); - Assert.Null (container2.SubViews.ElementAt (0).Margin.View); - Assert.Null (container2.SubViews.ElementAt (0).Border.View); - Assert.Null (container2.SubViews.ElementAt (0).Padding.View); - Assert.Null (view.SuperView); - - container2.Dispose (); - -#if DEBUG_IDISPOSABLE - Assert.Empty (View.Instances); -#endif - } - - [Fact] - [TestRespondersDisposed] - public void Dispose_View () - { - var view = new View (); - Assert.NotNull (view.Margin); - Assert.NotNull (view.Border); - Assert.NotNull (view.Padding); - -#if DEBUG_IDISPOSABLE - Assert.Single (View.Instances); -#endif - - view.Dispose (); - Assert.Null (view.Margin.View); - Assert.Null (view.Border.View); - Assert.Null (view.Padding.View); - } - - [Fact] - [TestRespondersDisposed] - public void Dispose_View_With_AdornmentViews () - { - var view = new View (); - Assert.NotNull (view.Margin); - Assert.NotNull (view.Border); - Assert.NotNull (view.Padding); - - view.Margin.GetOrCreateView (); - view.Border.GetOrCreateView (); - view.Padding.GetOrCreateView (); - Assert.NotNull (view.Margin.View); - Assert.NotNull (view.Border.View); - Assert.NotNull (view.Padding.View); - -#if DEBUG_IDISPOSABLE - Assert.Equal (4, View.Instances.Count); -#endif - - view.Dispose (); - Assert.Null (view.Margin.View); - Assert.Null (view.Border.View); - Assert.Null (view.Padding.View); - } - - [Fact] - public void Internal_Tests () - { - var rect = new Rectangle (1, 1, 10, 1); - var view = new View { Frame = rect }; - } - - [Fact] - [TestRespondersDisposed] - public void New_Initializes () - { - // Parameterless - var r = new View (); - Assert.NotNull (r); - Assert.True (r.Enabled); - Assert.True (r.Visible); - - Assert.False (r.CanFocus); - Assert.False (r.HasFocus); - Assert.Equal (new Rectangle (0, 0, 0, 0), r.Viewport); - Assert.Equal (new Rectangle (0, 0, 0, 0), r.Frame); - Assert.Null (r.Focused); - Assert.False (r.HasScheme); - Assert.NotNull (r.GetScheme ()); - Assert.Equal (r.GetScheme (), SchemeManager.GetSchemesForCurrentTheme () ["Base"]); - Assert.Equal (0, r.Width); - Assert.Equal (0, r.Height); - Assert.Equal (0, r.X); - Assert.Equal (0, r.Y); - Assert.False (r.IsCurrentTop); - Assert.Empty (r.Id); - Assert.Empty (r.SubViews); - Assert.Null (r.MouseHoldRepeat); - Assert.False (r.MousePositionTracking); - Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); - Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection); - r.Dispose (); - - // Empty Rect - r = new View { Frame = Rectangle.Empty }; - Assert.NotNull (r); - Assert.False (r.CanFocus); - Assert.False (r.HasFocus); - Assert.Equal (new Rectangle (0, 0, 0, 0), r.Viewport); - Assert.Equal (new Rectangle (0, 0, 0, 0), r.Frame); - Assert.Null (r.Focused); - Assert.False (r.HasScheme); - Assert.NotNull (r.GetScheme ()); - Assert.Equal (r.GetScheme (), SchemeManager.GetSchemesForCurrentTheme () ["Base"]); - Assert.Equal (0, r.Width); - Assert.Equal (0, r.Height); - Assert.Equal (0, r.X); - Assert.Equal (0, r.Y); - Assert.False (r.IsCurrentTop); - Assert.Empty (r.Id); - Assert.Empty (r.SubViews); - Assert.Null (r.MouseHoldRepeat); - Assert.False (r.MousePositionTracking); - Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); - Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection); - r.Dispose (); - - // Rect with values - r = new View { Frame = new Rectangle (1, 2, 3, 4) }; - Assert.NotNull (r); - Assert.False (r.CanFocus); - Assert.False (r.HasFocus); - Assert.Equal (new Rectangle (0, 0, 3, 4), r.Viewport); - Assert.Equal (new Rectangle (1, 2, 3, 4), r.Frame); - Assert.Null (r.Focused); - Assert.False (r.HasScheme); - Assert.NotNull (r.GetScheme ()); - Assert.Equal (r.GetScheme (), SchemeManager.GetSchemesForCurrentTheme () ["Base"]); - Assert.Equal (3, r.Width); - Assert.Equal (4, r.Height); - Assert.Equal (1, r.X); - Assert.Equal (2, r.Y); - Assert.False (r.IsCurrentTop); - Assert.Empty (r.Id); - Assert.Empty (r.SubViews); - Assert.Null (r.MouseHoldRepeat); - Assert.False (r.MousePositionTracking); - Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); - Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection); - r.Dispose (); - - // Initializes a view with a vertical direction - r = new View { Text = "Vertical View", TextDirection = TextDirection.TopBottom_LeftRight, Width = Dim.Auto (), Height = Dim.Auto () }; - r.TextFormatter.WordWrap = false; - Assert.NotNull (r); - - r.BeginInit (); - r.EndInit (); - Assert.False (r.CanFocus); - Assert.False (r.HasFocus); - Assert.Equal (new Rectangle (0, 0, 1, 13), r.Viewport); - Assert.Equal (new Rectangle (0, 0, 1, 13), r.Frame); - Assert.Null (r.Focused); - Assert.False (r.HasScheme); - Assert.NotNull (r.GetScheme ()); - Assert.Equal (r.GetScheme (), SchemeManager.GetSchemesForCurrentTheme () ["Base"]); - Assert.False (r.IsCurrentTop); - Assert.Equal (string.Empty, r.Id); - Assert.Empty (r.SubViews); - Assert.Null (r.MouseHoldRepeat); - Assert.False (r.MousePositionTracking); - Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); - Assert.Equal (TextDirection.TopBottom_LeftRight, r.TextDirection); - r.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void New_Methods_Return_False () - { - var r = new View (); - - Assert.False (r.NewKeyDownEvent (Key.Empty)); - - Assert.False (r.NewMouseEvent (new Mouse { Flags = MouseFlags.AllEvents })); - - r.Dispose (); - - // TODO: Add more - } - - [Fact] - [AutoInitShutdown] - public void Test_Nested_Views_With_Height_Equal_To_One () - { - var v = new View { Width = 11, Height = 3 }; - v.App = ApplicationImpl.Instance; - - var top = new View { Width = Dim.Fill (), Height = 1 }; - var bottom = new View { Width = Dim.Fill (), Height = 1, Y = 2 }; - - top.Add (new Label { Text = "111" }); - v.Add (top); - v.Add (new Line { Orientation = Orientation.Horizontal, Y = 1 }); - bottom.Add (new Label { Text = "222" }); - v.Add (bottom); - - v.BeginInit (); - v.EndInit (); - v.LayoutSubViews (); - v.Draw (); - - var looksLike = @" -111 -─────────── -222"; - DriverAssert.AssertDriverContentsAre (looksLike, _output); - v.Dispose (); - top.Dispose (); - bottom.Dispose (); - } - - [Fact] - [TestRespondersDisposed] - public void View_With_No_Difference_Between_An_Object_Initializer_Compute_And_A_Absolute () - { - // Object Initializer Computed - var view = new View { X = 1, Y = 2, Width = 3, Height = 4 }; - - // Object Initializer Absolute - var super = new View { Frame = new Rectangle (0, 0, 10, 10) }; - super.Add (view); - super.BeginInit (); - super.EndInit (); - super.LayoutSubViews (); - - Assert.Equal (1, view.X); - Assert.Equal (2, view.Y); - Assert.Equal (3, view.Width); - Assert.Equal (4, view.Height); - Assert.False (view.Frame.IsEmpty); - Assert.Equal (new Rectangle (1, 2, 3, 4), view.Frame); - Assert.False (view.Viewport.IsEmpty); - Assert.Equal (new Rectangle (0, 0, 3, 4), view.Viewport); - - view.LayoutSubViews (); - - Assert.Equal (1, view.X); - Assert.Equal (2, view.Y); - Assert.Equal (3, view.Width); - Assert.Equal (4, view.Height); - Assert.False (view.Frame.IsEmpty); - Assert.False (view.Viewport.IsEmpty); - super.Dispose (); - -#if DEBUG_IDISPOSABLE - Assert.Empty (View.Instances); -#endif - - // Default Constructor - view = new View (); - Assert.Equal (0, view.X); - Assert.Equal (0, view.Y); - Assert.Equal (0, view.Width); - Assert.Equal (0, view.Height); - Assert.True (view.Frame.IsEmpty); - Assert.True (view.Viewport.IsEmpty); - view.Dispose (); - - // Object Initializer - view = new View { X = 1, Y = 2, Text = "" }; - Assert.Equal (1, view.X); - Assert.Equal (2, view.Y); - Assert.Equal (0, view.Width); - Assert.Equal (0, view.Height); - Assert.False (view.Frame.IsEmpty); - Assert.True (view.Viewport.IsEmpty); - view.Dispose (); - - // Default Constructor and post assignment equivalent to Object Initializer - view = new View (); - view.X = 1; - view.Y = 2; - view.Width = 3; - view.Height = 4; - super = new View { Frame = new Rectangle (0, 0, 10, 10) }; - super.Add (view); - super.BeginInit (); - super.EndInit (); - super.LayoutSubViews (); - Assert.Equal (1, view.X); - Assert.Equal (2, view.Y); - Assert.Equal (3, view.Width); - Assert.Equal (4, view.Height); - Assert.False (view.Frame.IsEmpty); - Assert.Equal (new Rectangle (1, 2, 3, 4), view.Frame); - Assert.False (view.Viewport.IsEmpty); - Assert.Equal (new Rectangle (0, 0, 3, 4), view.Viewport); - super.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Visible_Clear_The_View_Output () - { - var view = new View { Text = "Testing visibility." }; // use View, not Label to avoid AutoSize == true - - Assert.Equal (0, view.Frame.Width); - Assert.Equal (0, view.Height); - var win = new Window (); - win.Add (view); - Runnable top = new (); - top.Add (win); - SessionToken rs = Application.Begin (top); - - view.Width = Dim.Auto (); - view.Height = Dim.Auto (); - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal ("Testing visibility.".Length, view.Frame.Width); - Assert.True (view.Visible); - Application.Driver!.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - - DriverAssert.AssertDriverContentsWithFrameAre (@" -┌────────────────────────────┐ -│Testing visibility. │ -│ │ -│ │ -└────────────────────────────┘ -", - _output); - - view.Visible = false; - - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre (@" -┌────────────────────────────┐ -│ │ -│ │ -│ │ -└────────────────────────────┘ -", - _output); - Application.End (rs); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Visible_Sets_Also_Sets_SubViews () - { - var button = new Button { Text = "Click Me" }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (button); - Runnable top = new (); - top.Add (win); - - var iterations = 0; - - Application.Iteration += OnApplicationOnIteration; - - Application.Run (top); - Application.Iteration -= OnApplicationOnIteration; - top.Dispose (); - Assert.Equal (1, iterations); - - return; - - void OnApplicationOnIteration (object s, EventArgs a) - { - iterations++; - - Assert.True (button.Visible); - Assert.True (button.CanFocus); - Assert.True (button.HasFocus); - Assert.True (win.Visible); - Assert.True (win.CanFocus); - Assert.True (win.HasFocus); - - win.Visible = false; - Assert.True (button.Visible); - Assert.True (button.CanFocus); - Assert.False (button.HasFocus); - Assert.False (win.Visible); - Assert.True (win.CanFocus); - Assert.False (win.HasFocus); - - button.SetFocus (); - Assert.False (button.HasFocus); - Assert.False (win.HasFocus); - - win.SetFocus (); - Assert.False (button.HasFocus); - Assert.False (win.HasFocus); - - win.Visible = true; - Assert.True (button.HasFocus); - Assert.True (win.HasFocus); - - Application.RequestStop (); - } - } -} diff --git a/Tests/UnitTests/Views/AppendAutocompleteTests.cs b/Tests/UnitTests/Views/AppendAutocompleteTests.cs deleted file mode 100644 index 784cdb4350..0000000000 --- a/Tests/UnitTests/Views/AppendAutocompleteTests.cs +++ /dev/null @@ -1,289 +0,0 @@ -namespace UnitTests.TextTests; - -public class AppendAutocompleteTests (ITestOutputHelper output) -{ - [Fact] - [AutoInitShutdown] - public void TestAutoAppend_AfterCloseKey_NoAutocomplete () - { - TextField tf = GetTextFieldsInViewSuggesting ("fish"); - - // f is typed and suggestion is "fish" - Application.RaiseKeyDownEvent ('f'); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("f", tf.Text); - - // When cancelling autocomplete - Application.RaiseKeyDownEvent (Key.Esc); - - // Suggestion should disappear - tf.Draw (); - tf.SetClipToScreen (); - DriverAssert.AssertDriverContentsAre ("f", output); - Assert.Equal ("f", tf.Text); - - // Still has focus though - Assert.Same (tf, Application.TopRunnableView.Focused); - - // But can tab away - Application.RaiseKeyDownEvent ('\t'); - Assert.NotSame (tf, Application.TopRunnableView.Focused); - Application.TopRunnableView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter () - { - TextField tf = GetTextFieldsInViewSuggesting ("fish"); - - // f is typed and suggestion is "fish" - Application.RaiseKeyDownEvent ('f'); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("f", tf.Text); - - // When cancelling autocomplete - Application.RaiseKeyDownEvent (Key.Esc); - - // Suggestion should disappear - tf.Draw (); - DriverAssert.AssertDriverContentsAre ("f", output); - Assert.Equal ("f", tf.Text); - - // Should reappear when you press next letter - Application.RaiseKeyDownEvent (Key.I); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("fi", tf.Text); - Application.TopRunnableView.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (KeyCode.CursorUp)] - [InlineData (KeyCode.CursorDown)] - public void TestAutoAppend_CycleSelections (KeyCode cycleKey) - { - TextField tf = GetTextFieldsInViewSuggesting ("fish", "friend"); - - // f is typed and suggestion is "fish" - Application.RaiseKeyDownEvent ('f'); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("f", tf.Text); - - // When cycling autocomplete - Application.RaiseKeyDownEvent (cycleKey); - - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("friend", output); - Assert.Equal ("f", tf.Text); - - // Should be able to cycle in circles endlessly - Application.RaiseKeyDownEvent (cycleKey); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("f", tf.Text); - Application.TopRunnableView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TestAutoAppend_NoRender_WhenCursorNotAtEnd () - { - TextField tf = GetTextFieldsInViewSuggesting ("fish"); - - // f is typed and suggestion is "fish" - Application.RaiseKeyDownEvent ('f'); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("f", tf.Text); - - // add a space then go back 1 - Application.RaiseKeyDownEvent (' '); - Application.RaiseKeyDownEvent (Key.CursorLeft); - - tf.SetClipToScreen (); - tf.Draw (); - DriverAssert.AssertDriverContentsAre ("f", output); - Assert.Equal ("f ", tf.Text); - Application.TopRunnableView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TestAutoAppend_NoRender_WhenNoMatch () - { - TextField tf = GetTextFieldsInViewSuggesting ("fish"); - - // f is typed and suggestion is "fish" - Application.RaiseKeyDownEvent ('f'); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("f", tf.Text); - - // x is typed and suggestion should disappear - Application.RaiseKeyDownEvent (Key.X); - tf.SetClipToScreen (); - tf.Draw (); - DriverAssert.AssertDriverContentsAre ("fx", output); - Assert.Equal ("fx", tf.Text); - Application.TopRunnableView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TestAutoAppend_ShowThenAccept_CasesDiffer () - { - TextField tf = GetTextFieldsInView (); - - tf.Autocomplete = new AppendAutocomplete (tf); - var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; - generator.AllSuggestions = new () { "FISH" }; - - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("", output); - tf.NewKeyDownEvent (Key.M); - tf.NewKeyDownEvent (Key.Y); - tf.NewKeyDownEvent (Key.Space); - tf.NewKeyDownEvent (Key.F); - Assert.Equal ("my f", tf.Text); - - // Even though there is no match on case we should still get the suggestion - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("my fISH", output); - Assert.Equal ("my f", tf.Text); - - // When tab completing the case of the whole suggestion should be applied - Application.RaiseKeyDownEvent ('\t'); - tf.SetClipToScreen (); - tf.Draw (); - DriverAssert.AssertDriverContentsAre ("my FISH", output); - Assert.Equal ("my FISH", tf.Text); - Application.TopRunnableView.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TestAutoAppend_ShowThenAccept_MatchCase () - { - TextField tf = GetTextFieldsInView (); - - tf.Autocomplete = new AppendAutocomplete (tf); - var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; - generator.AllSuggestions = new () { "fish" }; - - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("", output); - - tf.NewKeyDownEvent (new ('f')); - - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("f", tf.Text); - - Application.RaiseKeyDownEvent ('\t'); - - tf.SetClipToScreen (); - tf.Draw (); - DriverAssert.AssertDriverContentsAre ("fish", output); - Assert.Equal ("fish", tf.Text); - - // Tab should autcomplete but not move focus - Assert.Same (tf, Application.TopRunnableView.Focused); - - // Second tab should move focus (nothing to autocomplete) - Application.RaiseKeyDownEvent ('\t'); - Assert.NotSame (tf, Application.TopRunnableView.Focused); - Application.TopRunnableView.Dispose (); - } - - [Theory] - [AutoInitShutdown] - [InlineData ("ffffffffffffffffffffffffff", "ffffffffff")] - [InlineData ("f234567890", "f234567890")] - [InlineData ("fisérables", "fisérables")] - public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, string expectRender) - { - TextField tf = GetTextFieldsInViewSuggesting (overspillUsing); - - // f is typed we should only see 'f' up to size of View (10) - Application.RaiseKeyDownEvent ('f'); - tf.SetClipToScreen (); - tf.Draw (); - tf.SetClipToScreen (); - - DriverAssert.AssertDriverContentsAre (expectRender, output); - Assert.Equal ("f", tf.Text); - Application.TopRunnableView.Dispose (); - } - - private TextField GetTextFieldsInView () - { - var tf = new TextField { Width = 10 }; - var tf2 = new TextField { Y = 1, Width = 10 }; - - Runnable top = new (); - top.Add (tf); - top.Add (tf2); - - Application.Begin (top); - - Assert.Same (tf, top.Focused); - - return tf; - } - - private TextField GetTextFieldsInViewSuggesting (params string [] suggestions) - { - TextField tf = GetTextFieldsInView (); - - tf.Autocomplete = new AppendAutocomplete (tf); - var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; - generator.AllSuggestions = suggestions.ToList (); - - tf.Draw (); - - DriverAssert.AssertDriverContentsAre ("", output); - - return tf; - } -} diff --git a/Tests/UnitTests/Views/ButtonTests.cs b/Tests/UnitTests/Views/ButtonTests.cs deleted file mode 100644 index 8533b767f0..0000000000 --- a/Tests/UnitTests/Views/ButtonTests.cs +++ /dev/null @@ -1,207 +0,0 @@ -namespace UnitTests.ViewsTests; - -public class ButtonTests (ITestOutputHelper output) -{ - [Fact] - [SetupFakeApplication] - public void Constructors_Defaults () - { - // Override CM - Button.DefaultShadow = ShadowStyles.None; - - var btn = new Button () - { - App = ApplicationImpl.Instance - }; - Assert.Equal (string.Empty, btn.Text); - btn.BeginInit (); - btn.EndInit (); - btn.SetRelativeLayout (new (100, 100)); - - Assert.Equal ($"{Glyphs.LeftBracket} {Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.False (btn.IsDefault); - Assert.Equal (Alignment.Center, btn.TextAlignment); - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.True (btn.CanFocus); - Assert.Equal (new (0, 0, 4, 1), btn.Viewport); - Assert.Equal (new (0, 0, 4, 1), btn.Frame); - Assert.Equal ($"{Glyphs.LeftBracket} {Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.False (btn.IsDefault); - Assert.Equal (Alignment.Center, btn.TextAlignment); - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.True (btn.CanFocus); - Assert.Equal (new (0, 0, 4, 1), btn.Viewport); - Assert.Equal (new (0, 0, 4, 1), btn.Frame); - - Assert.Equal (string.Empty, btn.Title); - Assert.Equal (KeyCode.Null, btn.HotKey); - - btn.Draw (); - - var expected = @$" -{Glyphs.LeftBracket} {Glyphs.RightBracket} -"; - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - btn.Dispose (); - - btn = new () - { - App = ApplicationImpl.Instance, - Text = "_Test", IsDefault = true - }; - btn.Layout (); - Assert.Equal (new (10, 1), btn.TextFormatter.ConstrainToSize); - - btn.BeginInit (); - btn.EndInit (); - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.Equal (Key.T, btn.HotKey); - Assert.Equal ("_Test", btn.Text); - - Assert.Equal ( - $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} Test {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}", - btn.TextFormatter.Format () - ); - Assert.True (btn.IsDefault); - Assert.Equal (Alignment.Center, btn.TextAlignment); - Assert.True (btn.CanFocus); - - btn.SetRelativeLayout (new (100, 100)); - - // 0123456789012345678901234567890123456789 - // [* Test *] - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.Equal (10, btn.TextFormatter.Format ().Length); - Assert.Equal (new (10, 1), btn.TextFormatter.ConstrainToSize); - Assert.Equal (new (10, 1), btn.GetContentSize ()); - Assert.Equal (new (0, 0, 10, 1), btn.Viewport); - Assert.Equal (new (0, 0, 10, 1), btn.Frame); - Assert.Equal (KeyCode.T, btn.HotKey); - - btn.Dispose (); - - btn = new () - { - App = ApplicationImpl.Instance, - X = 1, Y = 2, Text = "_abc", IsDefault = true - }; - btn.BeginInit (); - btn.EndInit (); - Assert.Equal ("_abc", btn.Text); - Assert.Equal (Key.A, btn.HotKey); - - Assert.Equal ( - $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} abc {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}", - btn.TextFormatter.Format () - ); - Assert.True (btn.IsDefault); - Assert.Equal (Alignment.Center, btn.TextAlignment); - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.True (btn.CanFocus); - - ApplicationImpl.Instance.Driver?.ClearContents (); - btn.Draw (); - - expected = @$" - {Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} abc {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket} -"; - DriverAssert.AssertDriverContentsWithFrameAre (expected, output, ApplicationImpl.Instance.Driver); - - Assert.Equal (new (0, 0, 9, 1), btn.Viewport); - Assert.Equal (new (1, 2, 9, 1), btn.Frame); - btn.Dispose (); - } - - /// - /// This test demonstrates how to change the activation key for Button as described in the README.md keyboard - /// handling section - /// - [Fact] - [AutoInitShutdown] - public void KeyBindingExample () - { - var pressed = 0; - var btn = new Button { Text = "Press Me" }; - - btn.Accepting += (s, e) => pressed++; - - // The Button class supports the Default and Accept command - Assert.Contains (Command.HotKey, btn.GetSupportedCommands ()); - Assert.Contains (Command.Accept, btn.GetSupportedCommands ()); - - var top = new Runnable (); - top.Add (btn); - Application.Begin (top); - - Assert.True (btn.HasFocus); - - // default keybinding is Space which results in Command.Accept (when focused) - Application.RaiseKeyDownEvent (new ((KeyCode)' ')); - Assert.Equal (1, pressed); - - // remove the default keybinding (Space) - btn.KeyBindings.Clear (Command.HotKey); - btn.KeyBindings.Clear (Command.Accept); - - // After clearing the default keystroke the Space button no longer does anything for the Button - Application.RaiseKeyDownEvent (new ((KeyCode)' ')); - Assert.Equal (1, pressed); - - // Set a new binding of b for the click (Accept) event - btn.KeyBindings.Add (Key.B, Command.HotKey); // b will now trigger the Accept command (when focused or not) - - // now pressing B should call the button click event - Application.RaiseKeyDownEvent (Key.B); - Assert.Equal (2, pressed); - - // now pressing Shift-B should NOT call the button click event - Application.RaiseKeyDownEvent (Key.B.WithShift); - Assert.Equal (2, pressed); - - // now pressing Alt-B should NOT call the button click event - Application.RaiseKeyDownEvent (Key.B.WithAlt); - Assert.Equal (2, pressed); - - // now pressing Shift-Alt-B should NOT call the button click event - Application.RaiseKeyDownEvent (Key.B.WithAlt.WithShift); - Assert.Equal (2, pressed); - top.Dispose (); - } - - - [Fact] - [AutoInitShutdown] - public void Update_Parameterless_Only_On_Or_After_Initialize () - { - Button.DefaultShadow = ShadowStyles.None; - var btn = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "Say Hello 你" }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (btn); - var top = new Runnable (); - top.Add (win); - - Assert.False (btn.IsInitialized); - - Application.Begin (top); - Application.Driver?.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - Assert.True (btn.IsInitialized); - Assert.Equal ("Say Hello 你", btn.Text); - Assert.Equal ($"{Glyphs.LeftBracket} {btn.Text} {Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.Equal (new (0, 0, 16, 1), btn.Viewport); - var btnTxt = $"{Glyphs.LeftBracket} {btn.Text} {Glyphs.RightBracket}"; - - var expected = @$" -┌────────────────────────────┐ -│ │ -│ {btnTxt} │ -│ │ -└────────────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - top.Dispose (); - } - -} diff --git a/Tests/UnitTests/Views/CheckBoxTests.cs b/Tests/UnitTests/Views/CheckBoxTests.cs deleted file mode 100644 index 9e23efe706..0000000000 --- a/Tests/UnitTests/Views/CheckBoxTests.cs +++ /dev/null @@ -1,316 +0,0 @@ -// ReSharper disable AccessToModifiedClosure - -namespace UnitTests.ViewsTests; - -public class CheckBoxTests (ITestOutputHelper output) -{ - private static readonly Size _size25x1 = new (25, 1); - - - - #region Mouse Tests - - - - - - #endregion Mouse Tests - - [Fact] - [AutoInitShutdown] - public void TextAlignment_Centered () - { - var checkBox = new CheckBox - { - X = 1, - Y = Pos.Center (), - Text = "Check this out 你", - TextAlignment = Alignment.Center, - Width = 25 - }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill (), Title = "Test Demo 你" }; - win.Add (checkBox); - var top = new Runnable (); - top.Add (win); - - Application.Begin (top); - Application.Driver?.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - - Assert.Equal (Alignment.Center, checkBox.TextAlignment); - Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (_size25x1, checkBox.TextFormatter.ConstrainToSize); - - var expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {Glyphs.CheckStateUnChecked} Check this out 你 │ -│ │ -└────────────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - - checkBox.Value = CheckState.Checked; - Application.LayoutAndDraw (); - - expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {Glyphs.CheckStateChecked} Check this out 你 │ -│ │ -└────────────────────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TextAlignment_Justified () - { - var checkBox1 = new CheckBox - { - X = 1, - Y = Pos.Center (), - Text = "Check first out 你", - TextAlignment = Alignment.Fill, - Width = 25 - }; - - var checkBox2 = new CheckBox - { - X = 1, - Y = Pos.Bottom (checkBox1), - Text = "Check second out 你", - TextAlignment = Alignment.Fill, - Width = 25 - }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill (), Title = "Test Demo 你" }; - win.Add (checkBox1, checkBox2); - var top = new Runnable (); - top.Add (win); - - SessionToken rs = Application.Begin (top); - Application.Driver!.SetScreenSize (30, 6); - Application.LayoutAndDraw (); - Assert.Equal (Alignment.Fill, checkBox1.TextAlignment); - Assert.Equal (new (1, 1, 25, 1), checkBox1.Frame); - Assert.Equal (Alignment.Fill, checkBox2.TextAlignment); - Assert.Equal (new (1, 2, 25, 1), checkBox2.Frame); - - var expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {Glyphs.CheckStateUnChecked} Check first out 你 │ -│ {Glyphs.CheckStateUnChecked} Check second out 你 │ -│ │ -└────────────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 6), pos); - - checkBox1.Value = CheckState.Checked; - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (new (1, 1, 25, 1), checkBox1.Frame); - Assert.Equal (_size25x1, checkBox1.TextFormatter.ConstrainToSize); - - checkBox2.Value = CheckState.Checked; - AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (new (1, 2, 25, 1), checkBox2.Frame); - Assert.Equal (_size25x1, checkBox2.TextFormatter.ConstrainToSize); - AutoInitShutdownAttribute.RunIteration (); - - expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {Glyphs.CheckStateChecked} Check first out 你 │ -│ {Glyphs.CheckStateChecked} Check second out 你 │ -│ │ -└────────────────────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 6), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TextAlignment_Left () - { - var checkBox = new CheckBox - { - X = 1, - Y = Pos.Center (), - Text = "Check this out 你", - Width = 25 - }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill (), Title = "Test Demo 你" }; - win.Add (checkBox); - var top = new Runnable (); - top.Add (win); - - Application.Begin (top); - Application.Driver!.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - Assert.Equal (Alignment.Start, checkBox.TextAlignment); - Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (_size25x1, checkBox.TextFormatter.ConstrainToSize); - - var expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {Glyphs.CheckStateUnChecked} Check this out 你 │ -│ │ -└────────────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - - checkBox.Value = CheckState.Checked; - AutoInitShutdownAttribute.RunIteration (); - - expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {Glyphs.CheckStateChecked} Check this out 你 │ -│ │ -└────────────────────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void TextAlignment_Right () - { - var checkBox = new CheckBox - { - X = 1, - Y = Pos.Center (), - Text = "Check this out 你", - TextAlignment = Alignment.End, - Width = 25 - }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill (), Title = "Test Demo 你" }; - win.Add (checkBox); - var top = new Runnable (); - top.Add (win); - - Application.Begin (top); - Application.Driver!.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - Assert.Equal (Alignment.End, checkBox.TextAlignment); - Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (_size25x1, checkBox.TextFormatter.ConstrainToSize); - - var expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ Check this out 你 {Glyphs.CheckStateUnChecked} │ -│ │ -└────────────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - - checkBox.Value = CheckState.Checked; - AutoInitShutdownAttribute.RunIteration (); - - expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ Check this out 你 {Glyphs.CheckStateChecked} │ -│ │ -└────────────────────────────┘ -"; - - pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - top.Dispose (); - } - - [Fact] - public void HotKey_Command_Does_Not_Fire_Accept () - { - var cb = new CheckBox (); - var accepted = false; - - cb.Accepting += CheckBoxOnAccept; - cb.InvokeCommand (Command.HotKey); - - Assert.False (accepted); - - return; - - void CheckBoxOnAccept (object sender, CommandEventArgs e) { accepted = true; } - } - - [Theory] - [InlineData (CheckState.Checked)] - [InlineData (CheckState.UnChecked)] - [InlineData (CheckState.None)] - public void Activated_Handle_Event_Prevents_Change (CheckState initialState) - { - var ckb = new CheckBox { AllowCheckStateNone = true }; - var checkedInvoked = false; - - ckb.Value = initialState; - - ckb.Activating += OnActivating; - - Assert.Equal (initialState, ckb.Value); - bool? ret = ckb.InvokeCommand (Command.Activate); - Assert.True (ret); - Assert.True (checkedInvoked); - Assert.Equal (initialState, ckb.Value); - - return; - - void OnActivating (object sender, CommandEventArgs e) - { - checkedInvoked = true; - e.Handled = true; - } - } - - [Theory] - [InlineData (CheckState.Checked)] - [InlineData (CheckState.UnChecked)] - [InlineData (CheckState.None)] - public void ValueChanging_Cancel_Event_Prevents_Change (CheckState initialState) - { - var ckb = new CheckBox { AllowCheckStateNone = true }; - var checkedInvoked = false; - - ckb.Value = initialState; - - ckb.ValueChanging += OnValueChanging; - - Assert.Equal (initialState, ckb.Value); - - // AdvanceCheckState returns false if the state was changed, true if it was cancelled, null if it was not changed - bool? ret = ckb.AdvanceCheckState (); - Assert.True (ret); - Assert.True (checkedInvoked); - Assert.Equal (initialState, ckb.Value); - - return; - - void OnValueChanging (object sender, ValueChangingEventArgs e) - { - checkedInvoked = true; - e.Handled = true; - } - } -} diff --git a/Tests/UnitTests/Views/ColorPickerTests.cs b/Tests/UnitTests/Views/ColorPickerTests.cs deleted file mode 100644 index 3ed1358ef2..0000000000 --- a/Tests/UnitTests/Views/ColorPickerTests.cs +++ /dev/null @@ -1,9 +0,0 @@ -#nullable enable -using UnitTests; - -namespace UnitTests_Parallelizable.ViewsTests; - -public class ColorPickerTests -{ - -} diff --git a/Tests/UnitTests/Views/DatePickerTests.cs b/Tests/UnitTests/Views/DatePickerTests.cs deleted file mode 100644 index 7be8544f7d..0000000000 --- a/Tests/UnitTests/Views/DatePickerTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Globalization; -using UnitTests; - -namespace UnitTests.ViewsTests; - -public class DatePickerTests -{ - [Fact] - [AutoInitShutdown] - public void DatePicker_ShouldNot_SetDateOutOfRange_UsingNextMonthButton () - { - var date = new DateTime (9999, 11, 15); - var datePicker = new DatePicker (date); - - var top = new Runnable (); - top.Add (datePicker); - Application.Begin (top); - - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_dateEditor"), datePicker.Focused); - - // Set focus to next month button - datePicker.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_calendar"), datePicker.Focused); - datePicker.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_previousMonthButton"), datePicker.Focused); - datePicker.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_nextMonthButton"), datePicker.Focused); - - // Change month to December - Application.RaiseKeyDownEvent (Key.Enter); - Assert.Equal (12, datePicker.Value.Month); - - // Next month button is disabled, so focus advanced to edit field - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_previousMonthButton"), datePicker.Focused); - - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void DatePicker_ShouldNot_SetDateOutOfRange_UsingPreviousMonthButton () - { - var date = new DateTime (1, 2, 15); - var datePicker = new DatePicker (date); - var top = new Runnable (); - - // Move focus to previous month button - top.Add (datePicker); - Application.Begin (top); - - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_dateEditor"), datePicker.Focused); - - datePicker.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_calendar"), datePicker.Focused); - datePicker.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_previousMonthButton"), datePicker.Focused); - - // Change month to January - datePicker.NewKeyDownEvent (Key.Enter); - Assert.Equal (1, datePicker.Value.Month); - - // Next prev button is disabled, so focus advanced to edit button - Assert.Equal (datePicker.SubViews.First (v => v.Id == "_calendar"), datePicker.Focused); - - top.Dispose (); - } -} diff --git a/Tests/UnitTests/Views/DialogTests.cs b/Tests/UnitTests/Views/DialogTests.cs deleted file mode 100644 index d6bebb5ea9..0000000000 --- a/Tests/UnitTests/Views/DialogTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace ViewsTests; - -/// -/// Unit tests for that modify static properties and cannot run in parallel. -/// -public class DialogTests -{ - [Fact] - public void DefaultBorderStyle_Get_Set () - { - LineStyle original = Dialog.DefaultBorderStyle; - - try - { - Dialog.DefaultBorderStyle = LineStyle.Single; - Assert.Equal (LineStyle.Single, Dialog.DefaultBorderStyle); - - Dialog dialog = new (); - Assert.Equal (LineStyle.Single, dialog.BorderStyle); - dialog.Dispose (); - - Dialog.DefaultBorderStyle = LineStyle.Double; - Assert.Equal (LineStyle.Double, Dialog.DefaultBorderStyle); - - dialog = new (); - Assert.Equal (LineStyle.Double, dialog.BorderStyle); - dialog.Dispose (); - } - finally - { - Dialog.DefaultBorderStyle = original; - } - } - - [Fact] - public void DefaultButtonAlignment_Get_Set () - { - Alignment original = Dialog.DefaultButtonAlignment; - - try - { - Dialog.DefaultButtonAlignment = Alignment.Start; - Assert.Equal (Alignment.Start, Dialog.DefaultButtonAlignment); - - Dialog dialog = new (); - Assert.Equal (Alignment.Start, dialog.ButtonAlignment); - dialog.Dispose (); - - Dialog.DefaultButtonAlignment = Alignment.Center; - Assert.Equal (Alignment.Center, Dialog.DefaultButtonAlignment); - - dialog = new (); - Assert.Equal (Alignment.Center, dialog.ButtonAlignment); - dialog.Dispose (); - } - finally - { - Dialog.DefaultButtonAlignment = original; - } - } - - [Fact] - public void DefaultButtonAlignmentModes_Get_Set () - { - AlignmentModes original = Dialog.DefaultButtonAlignmentModes; - - try - { - Dialog.DefaultButtonAlignmentModes = AlignmentModes.StartToEnd; - Assert.Equal (AlignmentModes.StartToEnd, Dialog.DefaultButtonAlignmentModes); - - Dialog dialog = new (); - Assert.Equal (AlignmentModes.StartToEnd, dialog.ButtonAlignmentModes); - dialog.Dispose (); - - Dialog.DefaultButtonAlignmentModes = AlignmentModes.IgnoreFirstOrLast; - Assert.Equal (AlignmentModes.IgnoreFirstOrLast, Dialog.DefaultButtonAlignmentModes); - - dialog = new (); - Assert.Equal (AlignmentModes.IgnoreFirstOrLast, dialog.ButtonAlignmentModes); - dialog.Dispose (); - } - finally - { - Dialog.DefaultButtonAlignmentModes = original; - } - } - - [Fact] - public void DefaultShadow_Get_Set () - { - ShadowStyles original = Dialog.DefaultShadow; - - try - { - Dialog.DefaultShadow = ShadowStyles.None; - Assert.Equal (ShadowStyles.None, Dialog.DefaultShadow); - - Dialog dialog = new (); - Assert.Equal (ShadowStyles.None, dialog.ShadowStyle); - dialog.Dispose (); - - Dialog.DefaultShadow = ShadowStyles.Opaque; - Assert.Equal (ShadowStyles.Opaque, Dialog.DefaultShadow); - - dialog = new (); - Assert.Equal (ShadowStyles.Opaque, dialog.ShadowStyle); - dialog.Dispose (); - } - finally - { - Dialog.DefaultShadow = original; - } - } -} diff --git a/Tests/UnitTests/Views/FrameViewTests.cs b/Tests/UnitTests/Views/FrameViewTests.cs deleted file mode 100644 index 24ceafe5a0..0000000000 --- a/Tests/UnitTests/Views/FrameViewTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace UnitTests.ViewsTests; - -public class FrameViewTests (ITestOutputHelper output) -{ - [Fact] - public void Constructors_Defaults () - { - var fv = new FrameView (); - Assert.Equal (string.Empty, fv.Title); - Assert.Equal (string.Empty, fv.Text); - Assert.Equal (LineStyle.Rounded, fv.BorderStyle); - - fv = new () { Title = "Test" }; - Assert.Equal ("Test", fv.Title); - Assert.Equal (string.Empty, fv.Text); - Assert.Equal (LineStyle.Rounded, fv.BorderStyle); - - fv = new () - { - X = 1, - Y = 2, - Width = 10, - Height = 20, - Title = "Test" - }; - Assert.Equal ("Test", fv.Title); - Assert.Equal (string.Empty, fv.Text); - fv.BeginInit (); - fv.EndInit (); - Assert.Equal (LineStyle.Rounded, fv.BorderStyle); - Assert.Equal (new (1, 2, 10, 20), fv.Frame); - } - - [Fact] - [AutoInitShutdown] - public void Draw_Defaults () - { - Application.Driver!.SetScreenSize (10, 10); - var fv = new FrameView () { BorderStyle = LineStyle.Single }; - Assert.Equal (string.Empty, fv.Title); - Assert.Equal (string.Empty, fv.Text); - var top = new Runnable (); - top.Add (fv); - Application.Begin (top); - Assert.Equal (new (0, 0, 0, 0), fv.Frame); - DriverAssert.AssertDriverContentsWithFrameAre (@"", output); - - fv.Height = 5; - fv.Width = 5; - Assert.Equal (new (0, 0, 5, 5), fv.Frame); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌───┐ -│ │ -│ │ -│ │ -└───┘", - output - ); - - fv.X = 1; - fv.Y = 2; - Assert.Equal (new (1, 2, 5, 5), fv.Frame); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌───┐ - │ │ - │ │ - │ │ - └───┘", - output - ); - - fv.X = -1; - fv.Y = -2; - Assert.Equal (new (-1, -2, 5, 5), fv.Frame); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - │ - │ -───┘", - output - ); - - fv.X = 7; - fv.Y = 8; - Assert.Equal (new (7, 8, 5, 5), fv.Frame); - AutoInitShutdownAttribute.RunIteration (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" - ┌── - │ ", - output - ); - top.Dispose (); - } -} diff --git a/Tests/UnitTests/Views/GraphViewTests.cs b/Tests/UnitTests/Views/GraphViewTests.cs deleted file mode 100644 index b57c7c4d7e..0000000000 --- a/Tests/UnitTests/Views/GraphViewTests.cs +++ /dev/null @@ -1,1761 +0,0 @@ -using System.Text; - -namespace UnitTests.ViewsTests; - -#region Helper Classes - -internal class FakeHAxis : HorizontalAxis -{ - public List DrawAxisLinePoints = new (); - public List LabelPoints = new (); - - public override void DrawAxisLabel (GraphView graph, int screenPosition, string text) - { - base.DrawAxisLabel (graph, screenPosition, text); - LabelPoints.Add (screenPosition); - } - - protected override void DrawAxisLine (GraphView graph, int x, int y) - { - base.DrawAxisLine (graph, x, y); - DrawAxisLinePoints.Add (new Point (x, y)); - } -} - -internal class FakeVAxis : VerticalAxis -{ - public List DrawAxisLinePoints = new (); - public List LabelPoints = new (); - - public override void DrawAxisLabel (GraphView graph, int screenPosition, string text) - { - base.DrawAxisLabel (graph, screenPosition, text); - LabelPoints.Add (screenPosition); - } - - protected override void DrawAxisLine (GraphView graph, int x, int y) - { - base.DrawAxisLine (graph, x, y); - DrawAxisLinePoints.Add (new Point (x, y)); - } -} - -#endregion - -public class GraphViewTests : TestDriverBase -{ - /// - /// A cell size of 0 would result in mapping all graph space into the same cell of the console. Since - /// is mutable a sensible place to check this is in redraw. - /// - [Fact] - [AutoInitShutdown] - public void CellSizeZero () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - //gv.Scheme = new Scheme (); - gv.Viewport = new Rectangle (0, 0, 50, 30); - gv.Series.Add (new ScatterSeries { Points = new List { new (1, 1) } }); - gv.CellSize = new PointF (0, 5); - var ex = Assert.Throws (() => gv.Draw ()); - - Assert.Equal ("CellSize cannot be 0", ex.Message); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - /// Returns a basic very small graph (10 x 5) - /// - public static GraphView GetGraph () - { - var gv = new GraphView () - { - Driver = Application.Driver ?? CreateTestDriver () - }; - gv.BeginInit (); - gv.EndInit (); - - //gv.Scheme = new Scheme (); - gv.MarginBottom = 1; - gv.MarginLeft = 1; - gv.Viewport = new Rectangle (0, 0, 10, 5); - - return gv; - } - - - /// - /// Tests that each point in the screen space maps to a rectangle of (float) graph space and that each corner of - /// that rectangle of graph space maps back to the same row/col of the graph that was fed in - /// - [Fact] - public void TestReversing_ViewportToGraphSpace () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - gv.Viewport = new Rectangle (0, 0, 50, 30); - - // How much graph space each cell of the console depicts - gv.CellSize = new PointF (0.1f, 0.25f); - gv.AxisX.Increment = 1; - gv.AxisX.ShowLabelsEvery = 1; - - gv.AxisY.Increment = 1; - gv.AxisY.ShowLabelsEvery = 1; - - // Start the graph at 80 - gv.ScrollOffset = new PointF (0, 80); - - for (var x = 0; x < gv.Viewport.Width; x++) - { - for (var y = 0; y < gv.Viewport.Height; y++) - { - RectangleF graphSpace = gv.ViewportToGraphSpace (x, y); - - // See - // https://en.wikipedia.org/wiki/Machine_epsilon - var epsilon = 0.0001f; - - Point p = gv.GraphSpaceToViewport ( - new PointF ( - graphSpace.Left + epsilon, - graphSpace.Top + epsilon - ) - ); - Assert.Equal (x, p.X); - Assert.Equal (y, p.Y); - - p = gv.GraphSpaceToViewport ( - new PointF ( - graphSpace.Right - epsilon, - graphSpace.Top + epsilon - ) - ); - Assert.Equal (x, p.X); - Assert.Equal (y, p.Y); - - p = gv.GraphSpaceToViewport ( - new PointF ( - graphSpace.Left + epsilon, - graphSpace.Bottom - epsilon - ) - ); - Assert.Equal (x, p.X); - Assert.Equal (y, p.Y); - - p = gv.GraphSpaceToViewport ( - new PointF ( - graphSpace.Right - epsilon, - graphSpace.Bottom - epsilon - ) - ); - Assert.Equal (x, p.X); - Assert.Equal (y, p.Y); - } - } - } - - #region Screen to Graph Tests - - [Fact] - public void ViewportToGraphSpace_DefaultCellSize () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - // origin should be bottom left - RectangleF botLeft = gv.ViewportToGraphSpace (0, 9); - Assert.Equal (0, botLeft.X); - Assert.Equal (0, botLeft.Y); - Assert.Equal (1, botLeft.Width); - Assert.Equal (1, botLeft.Height); - - // up 2 rows of the console and along 1 col - RectangleF up2along1 = gv.ViewportToGraphSpace (1, 7); - Assert.Equal (1, up2along1.X); - Assert.Equal (2, up2along1.Y); - } - - [Fact] - public void ViewportToGraphSpace_DefaultCellSize_WithMargin () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - // origin should be bottom left - RectangleF botLeft = gv.ViewportToGraphSpace (0, 9); - Assert.Equal (0, botLeft.X); - Assert.Equal (0, botLeft.Y); - Assert.Equal (1, botLeft.Width); - Assert.Equal (1, botLeft.Height); - - gv.MarginLeft = 1; - - botLeft = gv.ViewportToGraphSpace (0, 9); - - // Origin should be at 1,9 now to leave a margin of 1 - // so screen position 0,9 would be data space -1,0 - Assert.Equal (-1, botLeft.X); - Assert.Equal (0, botLeft.Y); - Assert.Equal (1, botLeft.Width); - Assert.Equal (1, botLeft.Height); - - gv.MarginLeft = 1; - gv.MarginBottom = 1; - - botLeft = gv.ViewportToGraphSpace (0, 9); - - // Origin should be at 1,0 (to leave a margin of 1 in both sides) - // so screen position 0,9 would be data space -1,-1 - Assert.Equal (-1, botLeft.X); - Assert.Equal (-1, botLeft.Y); - Assert.Equal (1, botLeft.Width); - Assert.Equal (1, botLeft.Height); - } - - [Fact] - public void ViewportToGraphSpace_CustomCellSize () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - // Each cell of screen measures 5 units in graph data model vertically and 1/4 horizontally - gv.CellSize = new PointF (0.25f, 5); - - // origin should be bottom left - // (note that y=10 is actually overspilling the control, the last row is 9) - RectangleF botLeft = gv.ViewportToGraphSpace (0, 9); - Assert.Equal (0, botLeft.X); - Assert.Equal (0, botLeft.Y); - Assert.Equal (0.25f, botLeft.Width); - Assert.Equal (5, botLeft.Height); - - // up 2 rows of the console and along 1 col - RectangleF up2along1 = gv.ViewportToGraphSpace (1, 7); - Assert.Equal (0.25f, up2along1.X); - Assert.Equal (10, up2along1.Y); - Assert.Equal (0.25f, botLeft.Width); - Assert.Equal (5, botLeft.Height); - } - - #endregion - - #region Graph to Screen Tests - - [Fact] - public void GraphSpaceToViewport_DefaultCellSize () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - // origin should be bottom left - Point botLeft = gv.GraphSpaceToViewport (new PointF (0, 0)); - Assert.Equal (0, botLeft.X); - Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left - - // along 2 and up 1 in graph space - Point along2up1 = gv.GraphSpaceToViewport (new PointF (2, 1)); - Assert.Equal (2, along2up1.X); - Assert.Equal (8, along2up1.Y); - } - - [Fact] - public void GraphSpaceToViewport_DefaultCellSize_WithMargin () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - // origin should be bottom left - Point botLeft = gv.GraphSpaceToViewport (new PointF (0, 0)); - Assert.Equal (0, botLeft.X); - Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left - - gv.MarginLeft = 1; - - // With a margin of 1 the origin should be at x=1 y= 9 - botLeft = gv.GraphSpaceToViewport (new PointF (0, 0)); - Assert.Equal (1, botLeft.X); - Assert.Equal (9, botLeft.Y); // row 9 of the view is the bottom left - - gv.MarginLeft = 1; - gv.MarginBottom = 1; - - // With a margin of 1 in both directions the origin should be at x=1 y= 9 - botLeft = gv.GraphSpaceToViewport (new PointF (0, 0)); - Assert.Equal (1, botLeft.X); - Assert.Equal (8, botLeft.Y); // row 8 of the view is the bottom left up 1 cell - } - - [Fact] - public void GraphSpaceToViewport_ScrollOffset () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - //graph is scrolled to present chart space -5 to 5 in both axes - gv.ScrollOffset = new PointF (-5, -5); - - // origin should be right in the middle of the control - Point botLeft = gv.GraphSpaceToViewport (new PointF (0, 0)); - Assert.Equal (5, botLeft.X); - Assert.Equal (4, botLeft.Y); - - // along 2 and up 1 in graph space - Point along2up1 = gv.GraphSpaceToViewport (new PointF (2, 1)); - Assert.Equal (7, along2up1.X); - Assert.Equal (3, along2up1.Y); - } - - [Fact] - public void GraphSpaceToViewport_CustomCellSize () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - // Each cell of screen is responsible for rendering 5 units in graph data model - // vertically and 1/4 horizontally - gv.CellSize = new PointF (0.25f, 5); - - // origin should be bottom left - Point botLeft = gv.GraphSpaceToViewport (new PointF (0, 0)); - Assert.Equal (0, botLeft.X); - - // row 9 of the view is the bottom left (height is 10 so 0,1,2,3..9) - Assert.Equal (9, botLeft.Y); - - // along 2 and up 1 in graph space - Point along2up1 = gv.GraphSpaceToViewport (new PointF (2, 1)); - Assert.Equal (8, along2up1.X); - Assert.Equal (9, along2up1.Y); - - // Y value 4 should be rendered in bottom most row - Assert.Equal (9, gv.GraphSpaceToViewport (new PointF (2, 4)).Y); - - // Cell height is 5 so this is the first point of graph space that should - // be rendered in the graph in next row up (row 9) - Assert.Equal (8, gv.GraphSpaceToViewport (new PointF (2, 5)).Y); - - // More boundary testing for this cell size - Assert.Equal (8, gv.GraphSpaceToViewport (new PointF (2, 6)).Y); - Assert.Equal (8, gv.GraphSpaceToViewport (new PointF (2, 7)).Y); - Assert.Equal (8, gv.GraphSpaceToViewport (new PointF (2, 8)).Y); - Assert.Equal (8, gv.GraphSpaceToViewport (new PointF (2, 9)).Y); - Assert.Equal (7, gv.GraphSpaceToViewport (new PointF (2, 10)).Y); - Assert.Equal (7, gv.GraphSpaceToViewport (new PointF (2, 11)).Y); - } - - [Fact] - public void GraphSpaceToViewport_CustomCellSize_WithScrollOffset () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - gv.Viewport = new Rectangle (0, 0, 20, 10); - - // Each cell of screen is responsible for rendering 5 units in graph data model - // vertically and 1/4 horizontally - gv.CellSize = new PointF (0.25f, 5); - - //graph is scrolled to present some negative chart (4 negative cols and 2 negative rows) - gv.ScrollOffset = new PointF (-1, -10); - - // origin should be in the lower left (but not right at the bottom) - Point botLeft = gv.GraphSpaceToViewport (new PointF (0, 0)); - Assert.Equal (4, botLeft.X); - Assert.Equal (7, botLeft.Y); - - // along 2 and up 1 in graph space - Point along2up1 = gv.GraphSpaceToViewport (new PointF (2, 1)); - Assert.Equal (12, along2up1.X); - Assert.Equal (7, along2up1.Y); - - // More boundary testing for this cell size/offset - Assert.Equal (6, gv.GraphSpaceToViewport (new PointF (2, 6)).Y); - Assert.Equal (6, gv.GraphSpaceToViewport (new PointF (2, 7)).Y); - Assert.Equal (6, gv.GraphSpaceToViewport (new PointF (2, 8)).Y); - Assert.Equal (6, gv.GraphSpaceToViewport (new PointF (2, 9)).Y); - Assert.Equal (5, gv.GraphSpaceToViewport (new PointF (2, 10)).Y); - Assert.Equal (5, gv.GraphSpaceToViewport (new PointF (2, 11)).Y); - } - - #endregion -} - -public class SeriesTests -{ - [Fact] - [AutoInitShutdown] - public void Series_GetsPassedCorrectViewport_AllAtOnce () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - gv.Viewport = new Rectangle (0, 0, 50, 30); - //gv.Scheme = new Scheme (); - - var fullGraphBounds = RectangleF.Empty; - var graphScreenBounds = Rectangle.Empty; - - var series = new FakeSeries ( - (v, s, g) => - { - graphScreenBounds = s; - fullGraphBounds = g; - } - ); - gv.Series.Add (series); - - gv.LayoutSubViews (); - gv.Draw (); - Assert.Equal (new RectangleF (0, 0, 50, 30), fullGraphBounds); - Assert.Equal (new Rectangle (0, 0, 50, 30), graphScreenBounds); - - // Now we put a margin in - // Graph should not spill into the margins - - gv.MarginBottom = 2; - gv.MarginLeft = 5; - - // Even with a margin the graph should be drawn from - // the origin, we just get less visible width/height - gv.LayoutSubViews (); - gv.SetNeedsDraw (); - gv.Draw (); - Assert.Equal (new RectangleF (0, 0, 45, 28), fullGraphBounds); - - // The screen space the graph will be rendered into should - // not overspill the margins - Assert.Equal (new Rectangle (5, 0, 45, 28), graphScreenBounds); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - /// - /// Tests that the bounds passed to the ISeries for drawing into are correct even when the - /// results in multiple units of graph space being condensed into each cell of console - /// - [Fact] - [AutoInitShutdown] - public void Series_GetsPassedCorrectViewport_AllAtOnce_LargeCellSize () - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - //gv.Scheme = new Scheme (); - gv.Viewport = new Rectangle (0, 0, 50, 30); - - // the larger the cell size the more condensed (smaller) the graph space is - gv.CellSize = new PointF (2, 5); - - var fullGraphBounds = RectangleF.Empty; - var graphScreenBounds = Rectangle.Empty; - - var series = new FakeSeries ( - (v, s, g) => - { - graphScreenBounds = s; - fullGraphBounds = g; - } - ); - - gv.Series.Add (series); - - gv.LayoutSubViews (); - gv.Draw (); - - // Since each cell of the console is 2x5 of graph space the graph - // bounds to be rendered are larger - Assert.Equal (new RectangleF (0, 0, 100, 150), fullGraphBounds); - Assert.Equal (new Rectangle (0, 0, 50, 30), graphScreenBounds); - - // Graph should not spill into the margins - - gv.MarginBottom = 2; - gv.MarginLeft = 5; - - // Even with a margin the graph should be drawn from - // the origin, we just get less visible width/height - gv.LayoutSubViews (); - gv.SetNeedsDraw (); - gv.Draw (); - Assert.Equal (new RectangleF (0, 0, 90, 140), fullGraphBounds); - - // The screen space the graph will be rendered into should - // not overspill the margins - Assert.Equal (new Rectangle (5, 0, 45, 28), graphScreenBounds); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - private class FakeSeries : ISeries - { - private readonly Action _drawSeries; - - public FakeSeries ( - Action drawSeries - ) - { - _drawSeries = drawSeries; - } - - public void DrawSeries (GraphView graph, Rectangle bounds, RectangleF graphBounds) { _drawSeries (graph, bounds, graphBounds); } - } -} - -public class MultiBarSeriesTests -{ - private readonly ITestOutputHelper _output; - public MultiBarSeriesTests (ITestOutputHelper output) { _output = output; } - - [Fact] - public void MultiBarSeries_BarSpacing () - { - // Creates clusters of 5 adjacent bars with 2 spaces between clusters - var series = new MultiBarSeries (5, 7, 1); - - Assert.Equal (5, series.SubSeries.Count); - - Assert.Equal (0, series.SubSeries.ElementAt (0).Offset); - Assert.Equal (1, series.SubSeries.ElementAt (1).Offset); - Assert.Equal (2, series.SubSeries.ElementAt (2).Offset); - Assert.Equal (3, series.SubSeries.ElementAt (3).Offset); - Assert.Equal (4, series.SubSeries.ElementAt (4).Offset); - } - - [Fact] - public void MultiBarSeriesAddValues_WrongNumber () - { - // user asks for 3 bars per category - var series = new MultiBarSeries (3, 7, 1); - - var ex = Assert.Throws (() => series.AddBars ("Cars", (Rune)'#', 1)); - - Assert.Equal ( - "Number of values must match the number of bars per category (Parameter 'values')", - ex.Message - ); - } - - [Fact] - public void MultiBarSeriesColors_RightNumber () - { - Attribute [] colors = - { - new (Color.Green, Color.Black), new (Color.Green, Color.White), new (Color.BrightYellow, Color.White) - }; - - // user passes 3 colors and asks for 3 bars - var series = new MultiBarSeries (3, 7, 1, colors); - - Assert.Equal (series.SubSeries.ElementAt (0).OverrideBarColor, colors [0]); - Assert.Equal (series.SubSeries.ElementAt (1).OverrideBarColor, colors [1]); - Assert.Equal (series.SubSeries.ElementAt (2).OverrideBarColor, colors [2]); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - public void MultiBarSeriesColors_WrongNumber () - { - Attribute [] colors = { new (Color.Green, Color.Black) }; - - // user passes 1 color only but asks for 5 bars - var ex = Assert.Throws (() => new MultiBarSeries (5, 7, 1, colors)); - - Assert.Equal ( - "Number of colors must match the number of bars (Parameter 'numberOfBarsPerCategory')", - ex.Message - ); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestRendering_MultibarSeries () - { - var gv = new GraphView () - { - App = ApplicationImpl.Instance, - }; - //gv.Scheme = new Scheme (); - - // y axis goes from 0.1 to 1 across 10 console rows - // x axis goes from 0 to 20 across 20 console columns - gv.Viewport = new Rectangle (0, 0, 20, 10); - gv.CellSize = new PointF (1f, 0.1f); - gv.MarginBottom = 1; - gv.MarginLeft = 1; - - var multibarSeries = new MultiBarSeries (2, 4, 1); - - //nudge them left to avoid float rounding errors at the boundaries of cells - foreach (BarSeries sub in multibarSeries.SubSeries) - { - sub.Offset -= 0.001f; - } - - gv.Series.Add (multibarSeries); - - FakeHAxis fakeXAxis; - - // don't show axis labels that means any labels - // that appear are explicitly from the bars - gv.AxisX = fakeXAxis = new FakeHAxis { Increment = 0 }; - gv.AxisY = new FakeVAxis { Increment = 0 }; - - gv.LayoutSubViews (); - gv.Draw (); - - // Since bar series has no bars yet no labels should be displayed - Assert.Empty (fakeXAxis.LabelPoints); - - multibarSeries.AddBars ("hey", (Rune)'M', 0.5001f, 0.5001f); - fakeXAxis.LabelPoints.Clear (); - gv.LayoutSubViews (); - gv.SetNeedsDraw (); - gv.Draw (); - - Assert.Equal (4, fakeXAxis.LabelPoints.Single ()); - - multibarSeries.AddBars ("there", (Rune)'M', 0.24999f, 0.74999f); - multibarSeries.AddBars ("bob", (Rune)'M', 1, 2); - fakeXAxis.LabelPoints.Clear (); - gv.LayoutSubViews (); - gv.SetNeedsDraw (); - gv.SetClipToScreen (); - gv.Draw (); - - Assert.Equal (3, fakeXAxis.LabelPoints.Count); - Assert.Equal (4, fakeXAxis.LabelPoints [0]); - Assert.Equal (8, fakeXAxis.LabelPoints [1]); - Assert.Equal (12, fakeXAxis.LabelPoints [2]); - - var looksLike = - @" - │ MM - │ M MM - │ M MM - │ MM M MM - │ MM M MM - │ MM M MM - │ MM MM MM - │ MM MM MM - ┼──┬M──┬M──┬M────── - heytherebob "; - DriverAssert.AssertDriverContentsAre (looksLike, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } -} - -public class BarSeriesTests : TestDriverBase -{ - [Fact] - public void TestOneLongOneShortHorizontalBars_WithOffset () - { - GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY); - graph.Driver = CreateTestDriver (); - graph.Draw (); - - // no bars - Assert.Empty (barSeries.BarScreenStarts); - Assert.Empty (axisX.LabelPoints); - Assert.Empty (axisY.LabelPoints); - - // 0.1 units of graph y fit every screen row - // so 1 unit of graph y space is 10 screen rows - graph.CellSize = new PointF (0.5f, 0.1f); - - // Start bar 3 screen units up (y = height-3) - barSeries.Offset = 0.25f; - - // 1 bar every 3 rows of screen - barSeries.BarEvery = 0.3f; - barSeries.Orientation = Orientation.Horizontal; - - // 1 bar that is very wide (100 graph units horizontally = screen pos 50 but bounded by screen) - barSeries.Bars.Add ( - new BarSeriesBar ("hi1", new GraphCellToRender ((Rune)'.'), 100) - ); - - // 1 bar that is shorter - barSeries.Bars.Add ( - new BarSeriesBar ("hi2", new GraphCellToRender ((Rune)'.'), 5) - ); - - // redraw graph - graph.SetNeedsDraw (); - graph.Draw (); - - // since bars are horizontal all have the same X start cordinates - Assert.Equal (0, barSeries.BarScreenStarts [0].X); - Assert.Equal (0, barSeries.BarScreenStarts [1].X); - - // bar goes all the way to the end so bumps up against right screen boundary - // width of graph is 20 - Assert.Equal (19, barSeries.BarScreenEnds [0].X); - - // shorter bar is 5 graph units wide which is 10 screen units - Assert.Equal (10, barSeries.BarScreenEnds [1].X); - - // first bar should be offset 6 screen units (0.25f + 0.3f graph units) - // since height of control is 10 then first bar should be at screen row 4 (10-6) - Assert.Equal (4, barSeries.BarScreenStarts [0].Y); - - // second bar should be offset 9 screen units (0.25f + 0.6f graph units) - // since height of control is 10 then second bar should be at screen row 1 (10-9) - Assert.Equal (1, barSeries.BarScreenStarts [1].Y); - - // both bars should have labels but on the y axis - Assert.Equal (2, axisY.LabelPoints.Count); - Assert.Empty (axisX.LabelPoints); - - // labels should align with the bars (same screen y axis point) - Assert.Contains (4, axisY.LabelPoints); - Assert.Contains (1, axisY.LabelPoints); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestTwoTallBars_WithOffset () - { - GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY); - graph.Draw (); - - // no bars - Assert.Empty (barSeries.BarScreenStarts); - Assert.Empty (axisX.LabelPoints); - Assert.Empty (axisY.LabelPoints); - - // 0.5 units of graph fit every screen cell - // so 1 unit of graph space is 2 screen columns - graph.CellSize = new PointF (0.5f, 0.1f); - - // Start bar 1 screen unit along - barSeries.Offset = 0.5f; - barSeries.BarEvery = 1f; - - barSeries.Bars.Add ( - new BarSeriesBar ("hi1", new GraphCellToRender ((Rune)'.'), 100) - ); - - barSeries.Bars.Add ( - new BarSeriesBar ("hi2", new GraphCellToRender ((Rune)'.'), 100) - ); - - barSeries.Orientation = Orientation.Vertical; - - // redraw graph - graph.SetNeedsDraw (); - graph.Draw (); - - // bar should be drawn at BarEvery 1f + offset 0.5f = 3 screen units - Assert.Equal (3, barSeries.BarScreenStarts [0].X); - Assert.Equal (3, barSeries.BarScreenEnds [0].X); - - // second bar should be BarEveryx2 = 2f + offset 0.5f = 5 screen units - Assert.Equal (5, barSeries.BarScreenStarts [1].X); - Assert.Equal (5, barSeries.BarScreenEnds [1].X); - - // both bars should have labels - Assert.Equal (2, axisX.LabelPoints.Count); - Assert.Contains (3, axisX.LabelPoints); - Assert.Contains (5, axisX.LabelPoints); - - // bars are very tall but should not draw up off top of screen - Assert.Equal (9, barSeries.BarScreenStarts [0].Y); - Assert.Equal (0, barSeries.BarScreenEnds [0].Y); - Assert.Equal (9, barSeries.BarScreenStarts [1].Y); - Assert.Equal (0, barSeries.BarScreenEnds [1].Y); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestZeroHeightBar_WithName () - { - GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY); - graph.Draw (); - - // no bars - Assert.Empty (barSeries.BarScreenStarts); - Assert.Empty (axisX.LabelPoints); - Assert.Empty (axisY.LabelPoints); - - // bar of height 0 - barSeries.Bars.Add (new BarSeriesBar ("hi", new GraphCellToRender ((Rune)'.'), 0)); - barSeries.Orientation = Orientation.Vertical; - - // redraw graph - graph.SetNeedsDraw (); - graph.Draw (); - - // bar should not be drawn - Assert.Empty (barSeries.BarScreenStarts); - - Assert.NotEmpty (axisX.LabelPoints); - Assert.Empty (axisY.LabelPoints); - - // but bar name should be - // Screen position x=2 because bars are drawn every 1f of - // graph space and CellSize.X is 0.5f - Assert.Contains (2, axisX.LabelPoints); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - private GraphView GetGraph (out FakeBarSeries series, out FakeHAxis axisX, out FakeVAxis axisY) - { - var gv = new GraphView (); - gv.BeginInit (); - gv.EndInit (); - - // y axis goes from 0.1 to 1 across 10 console rows - // x axis goes from 0 to 10 across 20 console columns - gv.Viewport = new Rectangle (0, 0, 20, 10); - //gv.Scheme = new Scheme (); - gv.CellSize = new PointF (0.5f, 0.1f); - - gv.Series.Add (series = new FakeBarSeries ()); - - // don't show axis labels that means any labels - // that appaer are explicitly from the bars - gv.AxisX = axisX = new FakeHAxis { Increment = 0 }; - gv.AxisY = axisY = new FakeVAxis { Increment = 0 }; - - return gv; - } - - private class FakeBarSeries : BarSeries - { - public List BarScreenEnds { get; } = new (); - public List BarScreenStarts { get; } = new (); - public GraphCellToRender FinalColor { get; private set; } - protected override GraphCellToRender AdjustColor (GraphCellToRender graphCellToRender) { return FinalColor = base.AdjustColor (graphCellToRender); } - - protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn) - { - base.DrawBarLine (graph, start, end, beingDrawn); - - BarScreenStarts.Add (start); - BarScreenEnds.Add (end); - } - } -} - -public class AxisTests -{ - private GraphView GetGraph (out FakeHAxis axis) { return GetGraph (out axis, out _); } - private GraphView GetGraph (out FakeVAxis axis) { return GetGraph (out _, out axis); } - - private GraphView GetGraph (out FakeHAxis axisX, out FakeVAxis axisY) - { - var gv = new GraphView (); - gv.Viewport = new Rectangle (0, 0, 50, 30); - // gv.Scheme = new Scheme (); - - // graph can't be completely empty or it won't draw - gv.Series.Add (new ScatterSeries ()); - - axisX = new FakeHAxis (); - axisY = new FakeVAxis (); - gv.AxisX = axisX; - gv.AxisY = axisY; - - return gv; - } - - #region HorizontalAxis Tests - - /// Tests that the horizontal axis is computed correctly and does not over spill it's bounds - [Fact] - [AutoInitShutdown] - public void TestHAxisLocation_NoMargin () - { - GraphView gv = GetGraph (out FakeHAxis axis); - gv.LayoutSubViews (); - gv.Draw (); - - Assert.DoesNotContain (new Point (-1, 29), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 29), axis.DrawAxisLinePoints); - Assert.Contains (new Point (1, 29), axis.DrawAxisLinePoints); - - Assert.Contains (new Point (48, 29), axis.DrawAxisLinePoints); - Assert.Contains (new Point (49, 29), axis.DrawAxisLinePoints); - Assert.DoesNotContain (new Point (50, 29), axis.DrawAxisLinePoints); - - Assert.InRange (axis.LabelPoints.Max (), 0, 49); - Assert.InRange (axis.LabelPoints.Min (), 0, 49); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestHAxisLocation_MarginBottom () - { - GraphView gv = GetGraph (out FakeHAxis axis); - - gv.MarginBottom = 10; - gv.LayoutSubViews (); - gv.Draw (); - - Assert.DoesNotContain (new Point (-1, 19), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 19), axis.DrawAxisLinePoints); - Assert.Contains (new Point (1, 19), axis.DrawAxisLinePoints); - - Assert.Contains (new Point (48, 19), axis.DrawAxisLinePoints); - Assert.Contains (new Point (49, 19), axis.DrawAxisLinePoints); - Assert.DoesNotContain (new Point (50, 19), axis.DrawAxisLinePoints); - - Assert.InRange (axis.LabelPoints.Max (), 0, 49); - Assert.InRange (axis.LabelPoints.Min (), 0, 49); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestHAxisLocation_MarginLeft () - { - GraphView gv = GetGraph (out FakeHAxis axis); - - gv.MarginLeft = 5; - gv.LayoutSubViews (); - gv.Draw (); - - Assert.DoesNotContain (new Point (4, 29), axis.DrawAxisLinePoints); - Assert.Contains (new Point (5, 29), axis.DrawAxisLinePoints); - Assert.Contains (new Point (6, 29), axis.DrawAxisLinePoints); - - Assert.Contains (new Point (48, 29), axis.DrawAxisLinePoints); - Assert.Contains (new Point (49, 29), axis.DrawAxisLinePoints); - Assert.DoesNotContain (new Point (50, 29), axis.DrawAxisLinePoints); - - // Axis lables should not be drawn in the margin - Assert.InRange (axis.LabelPoints.Max (), 5, 49); - Assert.InRange (axis.LabelPoints.Min (), 5, 49); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - #endregion - - #region VerticalAxisTests - - /// Tests that the horizontal axis is computed correctly and does not over spill it's bounds - [Fact] - [AutoInitShutdown] - public void TestVAxisLocation_NoMargin () - { - GraphView gv = GetGraph (out FakeVAxis axis); - - gv.LayoutSubViews (); - gv.Draw (); - - Assert.DoesNotContain (new Point (0, -1), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 1), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 2), axis.DrawAxisLinePoints); - - Assert.Contains (new Point (0, 28), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 29), axis.DrawAxisLinePoints); - Assert.DoesNotContain (new Point (0, 30), axis.DrawAxisLinePoints); - - Assert.InRange (axis.LabelPoints.Max (), 0, 29); - Assert.InRange (axis.LabelPoints.Min (), 0, 29); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestVAxisLocation_MarginBottom () - { - GraphView gv = GetGraph (out FakeVAxis axis); - - gv.MarginBottom = 10; - gv.LayoutSubViews (); - gv.Draw (); - - Assert.DoesNotContain (new Point (0, -1), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 1), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 2), axis.DrawAxisLinePoints); - - Assert.Contains (new Point (0, 18), axis.DrawAxisLinePoints); - Assert.Contains (new Point (0, 19), axis.DrawAxisLinePoints); - Assert.DoesNotContain (new Point (0, 20), axis.DrawAxisLinePoints); - - // Labels should not be drawn into the axis - Assert.InRange (axis.LabelPoints.Max (), 0, 19); - Assert.InRange (axis.LabelPoints.Min (), 0, 19); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestVAxisLocation_MarginLeft () - { - GraphView gv = GetGraph (out FakeVAxis axis); - - gv.MarginLeft = 5; - gv.LayoutSubViews (); - gv.Draw (); - - Assert.DoesNotContain (new Point (5, -1), axis.DrawAxisLinePoints); - Assert.Contains (new Point (5, 1), axis.DrawAxisLinePoints); - Assert.Contains (new Point (5, 2), axis.DrawAxisLinePoints); - - Assert.Contains (new Point (5, 28), axis.DrawAxisLinePoints); - Assert.Contains (new Point (5, 29), axis.DrawAxisLinePoints); - Assert.DoesNotContain (new Point (5, 30), axis.DrawAxisLinePoints); - - Assert.InRange (axis.LabelPoints.Max (), 0, 29); - Assert.InRange (axis.LabelPoints.Min (), 0, 29); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - #endregion -} - -public class TextAnnotationTests -{ - private readonly ITestOutputHelper _output; - public TextAnnotationTests (ITestOutputHelper output) { _output = output; } - - [Theory] - [InlineData (null)] - [InlineData (" ")] - [InlineData ("\t\t")] - [AutoInitShutdown] - public void TestTextAnnotation_EmptyText (string whitespace) - { - GraphView gv = GraphViewTests.GetGraph (); - - gv.Annotations.Add ( - new TextAnnotation { Text = whitespace, GraphPosition = new PointF (4, 2) } - ); - - // add a point a bit further along the graph so if the whitespace were rendered - // the test would pick it up (AssertDriverContentsAre ignores trailing whitespace on lines) - var points = new ScatterSeries (); - points.Points.Add (new PointF (7, 2)); - gv.Series.Add (points); - gv.LayoutSubViews (); - gv.Draw (); - - var expected = - @$" - │ - ┤ {Glyphs.Dot} - ┤ -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestTextAnnotation_GraphUnits () - { - GraphView gv = GraphViewTests.GetGraph (); - - gv.Annotations.Add ( - new TextAnnotation { Text = "hey!", GraphPosition = new PointF (2, 2) } - ); - - gv.LayoutSubViews (); - gv.Draw (); - - var expected = - @" - │ - ┤ hey! - ┤ -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // user scrolls up one unit of graph space - gv.ScrollOffset = new PointF (0, 1f); - gv.SetNeedsDraw (); - gv.SetClipToScreen (); - gv.Draw (); - - // we expect the text annotation to go down one line since - // the scroll offset means that that point of graph space is - // lower down in the view. Note the 1 on the axis too, our viewport - // (excluding margins) now shows y of 1 to 4 (previously 0 to 5) - expected = - @" - │ - ┤ - ┤ hey! -1┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestTextAnnotation_LongText () - { - GraphView gv = GraphViewTests.GetGraph (); - - gv.Annotations.Add ( - new TextAnnotation - { - Text = "hey there partner hows it going boy its great", GraphPosition = new PointF (2, 2) - } - ); - - gv.LayoutSubViews (); - gv.SetNeedsDraw (); - gv.Draw (); - - // long text should get truncated - // margin takes up 1 units - // the GraphPosition of the anntation is 2 - // Leaving 7 characters of the annotation renderable (including space) - var expected = - @" - │ - ┤ hey the - ┤ -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestTextAnnotation_Offscreen () - { - GraphView gv = GraphViewTests.GetGraph (); - - gv.Annotations.Add ( - new TextAnnotation - { - Text = "hey there partner hows it going boy its great", GraphPosition = new PointF (9, 2) - } - ); - - gv.LayoutSubViews (); - gv.Draw (); - - // Text is off the screen (graph x axis runs to 8 not 9) - var expected = - @" - │ - ┤ - ┤ -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestTextAnnotation_ScreenUnits () - { - GraphView gv = GraphViewTests.GetGraph (); - - gv.Annotations.Add ( - new TextAnnotation { Text = "hey!", ScreenPosition = new Point (3, 1) } - ); - gv.LayoutSubViews (); - gv.SetClipToScreen (); - gv.Draw (); - - var expected = - @" - │ - ┤ hey! - ┤ -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // user scrolls up one unit of graph space - gv.ScrollOffset = new PointF (0, 1f); - gv.SetNeedsDraw (); - gv.SetClipToScreen (); - gv.Draw (); - - // we expect no change in the location of the annotation (only the axis label changes) - // this is because screen units are constant and do not change as the viewport into - // graph space scrolls to different areas of the graph - expected = - @" - │ - ┤ hey! - ┤ -1┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // user scrolls up one unit of graph space - gv.ScrollOffset = new PointF (0, 1f); - gv.SetNeedsDraw (); - gv.SetClipToScreen (); - gv.Draw (); - - // we expect no change in the location of the annotation (only the axis label changes) - // this is because screen units are constant and do not change as the viewport into - // graph space scrolls to different areas of the graph - expected = - @" - │ - ┤ hey! - ┤ -1┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } -} - -public class LegendTests -{ - private readonly ITestOutputHelper _output; - public LegendTests (ITestOutputHelper output) { _output = output; } - - [Fact] - public void Constructors_Defaults () - { - var legend = new LegendAnnotation (); - Assert.Equal (Rectangle.Empty, legend.Viewport); - Assert.Equal (Rectangle.Empty, legend.Frame); - Assert.Equal (LineStyle.Single, legend.BorderStyle); - Assert.False (legend.BeforeSeries); - - var bounds = new Rectangle (1, 2, 10, 3); - legend = new LegendAnnotation (bounds); - Assert.Equal (new Rectangle (0, 0, 8, 1), legend.Viewport); - Assert.Equal (bounds, legend.Frame); - Assert.Equal (LineStyle.Single, legend.BorderStyle); - Assert.False (legend.BeforeSeries); - legend.BorderStyle = LineStyle.None; - Assert.Equal (new Rectangle (0, 0, 10, 3), legend.Viewport); - Assert.Equal (bounds, legend.Frame); - } - - [Fact] - [AutoInitShutdown] - public void LegendNormalUsage_WithBorder () - { - GraphView gv = GraphViewTests.GetGraph (); - var legend = new LegendAnnotation (new Rectangle (2, 0, 5, 3)); - legend.AddEntry (new GraphCellToRender ((Rune)'A'), "Ant"); - legend.AddEntry (new GraphCellToRender ((Rune)'B'), "Bat"); - - gv.Annotations.Add (legend); - gv.Layout (); - gv.Draw (); - - var expected = - @" - │┌───┐ - ┤│AAn│ - ┤└───┘ -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void LegendNormalUsage_WithoutBorder () - { - GraphView gv = GraphViewTests.GetGraph (); - var legend = new LegendAnnotation (new Rectangle (2, 0, 5, 3)); - legend.AddEntry (new GraphCellToRender ((Rune)'A'), "Ant"); - legend.AddEntry (new GraphCellToRender ((Rune)'B'), "?"); // this will exercise pad - legend.AddEntry (new GraphCellToRender ((Rune)'C'), "Cat"); - legend.AddEntry (new GraphCellToRender ((Rune)'H'), "Hattter"); // not enough space for this oen - legend.BorderStyle = LineStyle.None; - - gv.Annotations.Add (legend); - - gv.Draw (); - - var expected = - @" - │AAnt - ┤B? - ┤CCat -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } -} - -public class PathAnnotationTests -{ - private readonly ITestOutputHelper _output; - public PathAnnotationTests (ITestOutputHelper output) { _output = output; } - - [Fact] - public void MarginBottom_BiggerThanHeight_ExpectBlankGraph () - { - GraphView gv = GraphViewTests.GetGraph (); - gv.Height = 10; - gv.MarginBottom = 20; - - gv.Series.Add ( - new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } } - ); - - gv.LayoutSubViews (); - gv.Draw (); - - var expected = - @" - - - "; - DriverAssert.AssertDriverContentsAre (expected, _output, gv.Driver); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - public void MarginLeft_BiggerThanWidth_ExpectBlankGraph () - { - GraphView gv = GraphViewTests.GetGraph (); - gv.Width = 10; - gv.MarginLeft = 20; - - gv.Series.Add ( - new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } } - ); - - gv.LayoutSubViews (); - gv.Draw (); - - var expected = - @" - - - "; - DriverAssert.AssertDriverContentsAre (expected, _output, gv.Driver); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void PathAnnotation_Box () - { - GraphView gv = GraphViewTests.GetGraph (); - - var path = new PathAnnotation (); - path.Points.Add (new PointF (1, 1)); - path.Points.Add (new PointF (1, 3)); - path.Points.Add (new PointF (6, 3)); - path.Points.Add (new PointF (6, 1)); - - // list the starting point again so that it draws a complete square - // (otherwise it will miss out the last line along the bottom) - path.Points.Add (new PointF (1, 1)); - - gv.Annotations.Add (path); - gv.LayoutSubViews (); - gv.Draw (); - - var expected = - @" - │...... - ┤. . - ┤...... -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void PathAnnotation_Diamond () - { - GraphView gv = GraphViewTests.GetGraph (); - - var path = new PathAnnotation (); - path.Points.Add (new PointF (1, 2)); - path.Points.Add (new PointF (3, 3)); - path.Points.Add (new PointF (6, 2)); - path.Points.Add (new PointF (3, 1)); - - // list the starting point again to close the shape - path.Points.Add (new PointF (1, 2)); - - gv.Annotations.Add (path); - - gv.LayoutSubViews (); - gv.Draw (); - - var expected = - @" - │ .. - ┤.. .. - ┤ ... -0┼┬┬┬┬┬┬┬┬ - 0 5"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Theory] - [AutoInitShutdown] - [InlineData (true)] - [InlineData (false)] - public void ViewChangeText_RendersCorrectly (bool useFill) - { - // create a wide window - var mount = new View { Width = 100, Height = 100 }; - var top = new Runnable (); - - try - { - // Create a view with a short text - var view = new View { Text = "ff", Width = 2, Height = 1 }; - - // Specify that the label should be very wide - if (useFill) - { - view.Width = Dim.Fill (); - } - else - { - view.Width = 100; - } - - //put label into view - mount.Add (view); - - //putting mount into Runnable since changing size - top.Add (mount); - Application.Begin (top); - - // render view - //view.Scheme = new Scheme (); - Assert.Equal (1, view.Height); - mount.SetNeedsDraw (); - mount.Draw (); - - // should have the initial text - DriverAssert.AssertDriverContentsAre ("ff", null); - - // change the text and redraw - view.Text = "ff1234"; - mount.SetNeedsDraw (); - top.SetClipToScreen (); - mount.Draw (); - - // should have the new text rendered - DriverAssert.AssertDriverContentsAre ("ff1234", null); - } - finally - { - top.Dispose (); - Application.Shutdown (); - } - } - - [Fact] - [AutoInitShutdown] - public void XAxisLabels_With_MarginLeft () - { - var gv = new GraphView - { - App = ApplicationImpl.Instance, - Viewport = new Rectangle (0, 0, 10, 7) - }; - - gv.CellSize = new PointF (1, 0.5f); - gv.AxisY.Increment = 1; - gv.AxisY.ShowLabelsEvery = 1; - - gv.Series.Add ( - new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } } - ); - - // reserve 3 cells of the left for the margin - gv.MarginLeft = 3; - gv.MarginBottom = 1; - - gv.LayoutSubViews (); - gv.SetNeedsDraw (); - gv.Draw (); - - var expected = - @$" - │ - 2┤ - │ - 1┤{Glyphs.Dot} - │ - 0┼┬┬┬┬{Glyphs.Dot}┬ - 0 5 - - "; - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void YAxisLabels_With_MarginBottom () - { - var gv = new GraphView - { - App = ApplicationImpl.Instance, - Viewport = new Rectangle (0, 0, 10, 7) - }; - - gv.CellSize = new PointF (1, 0.5f); - gv.AxisY.Increment = 1; - gv.AxisY.ShowLabelsEvery = 1; - - gv.Series.Add ( - new ScatterSeries { Points = { new PointF (1, 1), new PointF (5, 0) } } - ); - - // reserve 3 cells of the console for the margin - gv.MarginBottom = 3; - gv.MarginLeft = 1; - - gv.LayoutSubViews (); - gv.SetNeedsDraw (); - gv.Draw (); - - var expected = - @$" - │ -1┤{Glyphs.Dot} - │ -0┼┬┬┬┬{Glyphs.Dot}┬┬┬ - 0 5 - - "; - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } -} - -public class AxisIncrementToRenderTests -{ - [Fact] - public void AxisIncrementToRenderTests_Constructor () - { - var render = new AxisIncrementToRender (Orientation.Horizontal, 1, 6.6f); - - Assert.Equal (Orientation.Horizontal, render.Orientation); - Assert.Equal (1, render.ScreenLocation); - Assert.Equal (6.6f, render.Value); - } -} - -public class GraphViewBorderTests : TestDriverBase -{ - private readonly ITestOutputHelper _output; - public GraphViewBorderTests (ITestOutputHelper output) { _output = output; } - - [Fact] - [AutoInitShutdown] - public void GraphView_WithBorder_RendersCorrectly () - { - // Copilot - ChatGPT v4 - // This test reproduces the issue where GraphView assumes Frame == Bounds - // When a border is added, the graph content should be drawn inside the border, - // not overlapping it. - - GraphView gv = new () - { - X = 0, - Y = 0, - Width = 12, - Height = 7, - BorderStyle = LineStyle.Single, - Driver = Application.Driver - }; - - gv.BeginInit (); - gv.EndInit (); - - // Add some simple data - gv.Series.Add ( - new ScatterSeries - { - Points = new List - { - new (1, 1), - new (2, 2), - new (3, 3) - } - } - ); - - gv.MarginBottom = 1; - gv.MarginLeft = 1; - - gv.Draw (); - - // The border should be intact with corners properly drawn - // The graph content should not overwrite the border - // With BorderStyle.Single, the viewport shrinks by 1 on each side - // Then with MarginLeft=1 and MarginBottom=1, the graph data area starts at (2,1) in the View's frame - string expected = - @" -┌──────────┐ -│ │ ∙ │ -│ ┤ ∙ │ -│ ┤∙ │ -│0┼┬┬┬┬┬┬┬┬│ -│ 0 5 │ -└──────────┘"; - - DriverAssert.AssertDriverContentsAre (expected, _output); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void GraphView_Legend_WithBorder_UsesViewportNotFrame () - { - // Copilot - ChatGPT v4 - // This test verifies that when GraphView has adornments (border/margin/padding), - // legends and other annotations positioned using Viewport.Width work correctly. - - GraphView gv = new () - { - X = 0, - Y = 0, - Width = 30, - Height = 10, - BorderStyle = LineStyle.Single, - Driver = Application.Driver - }; - - gv.BeginInit (); - gv.EndInit (); - - // Add some data - gv.Series.Add ( - new ScatterSeries - { - Points = new List { new (1, 1), new (2, 2) } - } - ); - - gv.MarginBottom = 1; - gv.MarginLeft = 1; - - // WITH border: Viewport.Width == 28 (30 - 2), Frame.Width == 30 - // Legend positioned at Viewport.Width - 10 should work correctly - - LegendAnnotation legend = new (new (gv.Viewport.Width - 10, 0, 10, 3)); - legend.AddEntry (new GraphCellToRender ((Rune)'#'), "Test"); - gv.Annotations.Add (legend); - - gv.Draw (); - - // Verify the test runs without crashing - // The GraphView should render with its border intact and the legend positioned correctly inside - Assert.NotNull (gv); - Assert.Equal (28, gv.Viewport.Width); // 30 - 2 (for borders) - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } -} diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs deleted file mode 100644 index 25bed7467d..0000000000 --- a/Tests/UnitTests/Views/LabelTests.cs +++ /dev/null @@ -1,1178 +0,0 @@ -#nullable enable - -namespace UnitTests.ViewsTests; - -public class LabelTests (ITestOutputHelper output) -{ - [Fact] - [AutoInitShutdown] - public void Text_Set_With_AnchorEnd_Works () - { - var label = new Label { Y = Pos.Center (), Text = "Say Hello 你" }; - label.X = Pos.AnchorEnd (0) - Pos.Func (_ => label.TextFormatter.Text.GetColumns ()); - - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (label); - var top = new Runnable (); - top.Add (win); - - Application.Begin (top); - Application.Driver!.SetScreenSize (30, 5); - AutoInitShutdownAttribute.RunIteration (); - - var expected = @" -┌────────────────────────────┐ -│ │ -│ Say Hello 你│ -│ │ -└────────────────────────────┘ -"; - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - label.Text = "Say Hello 你 changed"; - - AutoInitShutdownAttribute.RunIteration (); - - expected = @" -┌────────────────────────────┐ -│ │ -│ Say Hello 你 changed│ -│ │ -└────────────────────────────┘ -"; - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Set_Text_With_Center () - { - var label = new Label { X = Pos.Center (), Y = Pos.Center (), Text = "Say Hello 你" }; - - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (label); - var top = new Runnable (); - top.Add (win); - - Application.Begin (top); - Application.Driver!.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - var expected = @" -┌────────────────────────────┐ -│ │ -│ Say Hello 你 │ -│ │ -└────────────────────────────┘ -"; - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - - label.Text = "Say Hello 你 changed"; - - AutoInitShutdownAttribute.RunIteration (); - - expected = @" -┌────────────────────────────┐ -│ │ -│ Say Hello 你 changed │ -│ │ -└────────────────────────────┘ -"; - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Label_Draw_Fill_Remaining () - { - Size tfSize = new (80, 1); - - Label label = new () { Text = "This label needs to be cleared before rewritten.", Width = tfSize.Width, Height = tfSize.Height }; - - TextFormatter tf1 = new () { Direction = TextDirection.LeftRight_TopBottom, ConstrainToSize = tfSize }; - tf1.Text = "This TextFormatter (tf1) without fill will not be cleared on rewritten."; - - TextFormatter tf2 = new () { Direction = TextDirection.LeftRight_TopBottom, ConstrainToSize = tfSize, FillRemaining = true }; - tf2.Text = "This TextFormatter (tf2) with fill will be cleared on rewritten."; - - Runnable top = new (); - top.Add (label); - SessionToken sessionToken = Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - - Assert.False (label.TextFormatter.FillRemaining); - Assert.False (tf1.FillRemaining); - Assert.True (tf2.FillRemaining); - - AutoInitShutdownAttribute.RunIteration (); - - tf1.Draw ( - Application.Driver, - new (new (0, 1), tfSize), - label.GetAttributeForRole (VisualRole.Normal), - label.GetAttributeForRole (VisualRole.HotNormal)); - - tf2.Draw ( - Application.Driver, - new (new (0, 2), tfSize), - label.GetAttributeForRole (VisualRole.Normal), - label.GetAttributeForRole (VisualRole.HotNormal)); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This label needs to be cleared before rewritten. -This TextFormatter (tf1) without fill will not be cleared on rewritten. -This TextFormatter (tf2) with fill will be cleared on rewritten. ", - output - ); - - Assert.False (label.NeedsDraw); - Assert.False (label.NeedsLayout); - Assert.False (label.SubViewNeedsDraw); - label.Text = "This label is rewritten."; - Assert.True (label.NeedsDraw); - Assert.True (label.NeedsLayout); - - //Assert.False (label.SubViewNeedsDraw); - label.Draw (); - - tf1.Text = "This TextFormatter (tf1) is rewritten."; - - tf1.Draw ( - Application.Driver, - new (new (0, 1), tfSize), - label.GetAttributeForRole (VisualRole.Normal), - label.GetAttributeForRole (VisualRole.HotNormal)); - - tf2.Text = "This TextFormatter (tf2) is rewritten."; - - tf2.Draw ( - Application.Driver, - new (new (0, 2), tfSize), - label.GetAttributeForRole (VisualRole.Normal), - label.GetAttributeForRole (VisualRole.HotNormal)); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -This label is rewritten. -This TextFormatter (tf1) is rewritten.will not be cleared on rewritten. -This TextFormatter (tf2) is rewritten. ", - output - ); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Label_Draw_Horizontal_Simple_Runes () - { - var label = new Label { Text = "Demo Simple Text" }; - var top = new Runnable (); - top.Add (label); - Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - - Assert.Equal (new (0, 0, 16, 1), label.Frame); - - var expected = @" -Demo Simple Text -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 16, 1), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Label_Draw_Vertical_Simple_Text () - { - var label = new Label { TextDirection = TextDirection.TopBottom_LeftRight, Text = "Demo Simple Text" }; - var top = new Runnable (); - top.Add (label); - Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - Assert.NotNull (label.Width); - Assert.NotNull (label.Height); - - var expected = @" -D -e -m -o - -S -i -m -p -l -e - -T -e -x -t -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 1, 16), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Label_Draw_Vertical_Wide_Runes () - { - var label = new Label { TextDirection = TextDirection.TopBottom_LeftRight, Text = "デモエムポンズ" }; - var top = new Runnable (); - top.Add (label); - Application.Begin (top); - AutoInitShutdownAttribute.RunIteration (); - - var expected = @" -デ -モ -エ -ム -ポ -ン -ズ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 2, 7), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Update_Only_On_Or_After_Initialize () - { - var label = new Label { X = Pos.Center (), Y = Pos.Center (), Text = "Say Hello 你" }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (label); - var top = new Runnable (); - top.Add (win); - - Assert.False (label.IsInitialized); - - Application.Begin (top); - Application.Driver!.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - Assert.True (label.IsInitialized); - Assert.Equal ("Say Hello 你", label.Text); - Assert.Equal ("Say Hello 你", label.TextFormatter.Text); - Assert.Equal (new (0, 0, 12, 1), label.Viewport); - - var expected = @" -┌────────────────────────────┐ -│ │ -│ Say Hello 你 │ -│ │ -└────────────────────────────┘ -"; - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void Update_Parameterless_Only_On_Or_After_Initialize () - { - var label = new Label { X = Pos.Center (), Y = Pos.Center (), Text = "Say Hello 你" }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (label); - var top = new Runnable (); - top.Add (win); - - Assert.False (label.IsInitialized); - - Application.Begin (top); - Application.Driver!.SetScreenSize (30, 5); - Application.LayoutAndDraw (); - Assert.True (label.IsInitialized); - Assert.Equal ("Say Hello 你", label.Text); - Assert.Equal ("Say Hello 你", label.TextFormatter.Text); - Assert.Equal (new (0, 0, 12, 1), label.Viewport); - - var expected = @" -┌────────────────────────────┐ -│ │ -│ Say Hello 你 │ -│ │ -└────────────────────────────┘ -"; - - Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 30, 5), pos); - top.Dispose (); - } - - [Fact] - [SetupFakeApplication] - public void Full_Border () - { - var label = new Label - { - Driver = Application.Driver, - BorderStyle = LineStyle.Single, Text = "Test" - }; - label.BeginInit (); - label.EndInit (); - label.SetRelativeLayout (Application.Screen.Size); - - Assert.Equal (new (0, 0, 4, 1), label.Viewport); - Assert.Equal (new (0, 0, 6, 3), label.Frame); - - label.Draw (); - - DriverAssert.AssertDriverContentsWithFrameAre ( - @" -┌┤Te├┐ -│Test│ -└────┘", - output - ); - label.Dispose (); - } - - // These tests were formally in AutoSizetrue.cs. They are (poor) Label tests. - private readonly string [] _expecteds = new string [21] - { - @" -┌────────────────────┐ -│View with long text │ -│ │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 0 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 1 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 2 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 3 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 4 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 5 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 6 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 7 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 8 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 9 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 10 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 11 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 12 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 13 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 14 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 15 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 16 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 17 │ -│Label 17 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 17 │ -│Label 18 │ -│Label 18 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 17 │ -│Label 18 │ -│Label 19 │ -│Label 19 │ -└────────────────────┘" - }; - - // TODO: This is a Label test. Move to label tests if there's not already a test for this. - [Fact] - [AutoInitShutdown] - public void AnchorEnd_Better_Than_Bottom_Equal_Inside_Window () - { - var win = new Window (); - - var label = new Label - { - Text = "This should be the last line.", - - //Width = Dim.Fill (), - X = 0, // keep unit test focused; don't use Center here - Y = Pos.AnchorEnd (1) - }; - - win.Add (label); - - Runnable top = new (); - top.Add (win); - SessionToken rs = Application.Begin (top); - Application.Driver!.SetScreenSize (40, 10); - Application.LayoutAndDraw (); - Assert.Equal (29, label.Text.Length); - Assert.Equal (new (0, 0, 40, 10), top.Frame); - Assert.Equal (new (0, 0, 40, 10), win.Frame); - Assert.Equal (new (0, 7, 29, 1), label.Frame); - - var expected = @" -┌──────────────────────────────────────┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│This should be the last line. │ -└──────────────────────────────────────┘ -" - ; - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - // TODO: This is a Label test. Move to label tests if there's not already a test for this. - [Fact] - [AutoInitShutdown] - public void Bottom_Equal_Inside_Window () - { - var win = new Window (); - - var label = new Label - { - Text = "This should be the last line.", - - //Width = Dim.Fill (), - X = 0, - Y = Pos.Bottom (win) - - 3 // two lines top and bottom borders more one line above the bottom border - }; - - win.Add (label); - - Runnable top = new (); - top.Add (win); - SessionToken rs = Application.Begin (top); - Application.Driver!.SetScreenSize (40, 10); - Application.LayoutAndDraw (); - Assert.Equal (new (0, 0, 40, 10), top.Frame); - Assert.Equal (new (0, 0, 40, 10), win.Frame); - Assert.Equal (new (0, 7, 29, 1), label.Frame); - - var expected = @" -┌──────────────────────────────────────┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│This should be the last line. │ -└──────────────────────────────────────┘ -"; - - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - - // TODO: This is a Dim test. Move to Dim tests. - - [Fact] - [AutoInitShutdown] - public void Dim_Subtract_Operator_With_Text () - { - Runnable top = new (); - - var view = new View - { - Text = "View with long text", - X = 0, - Y = 0, - Width = 20, - Height = 1 - }; - var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 }; - var count = 20; - - List