diff --git a/README.md b/README.md index 050c4f0..ec82d94 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![NuGet](https://img.shields.io/badge/NuGet-ConsolePlus.net-blue)](https://www.nuget.org/packages/ConsolePlus.net) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-512BD4)](https://dotnet.microsoft.com/) + [![NuGet](https://img.shields.io/nuget/v/ConsolePlus.net.svg?include_prereleases)](https://www.nuget.org/packages/ConsolePlus.net) [![Downloads](https://img.shields.io/nuget/dt/ConsolePlus.net)](https://www.nuget.org/packages/ConsolePlus.net) diff --git a/docs/adr/adr-config.adrplus b/adr-config.adrplus similarity index 77% rename from docs/adr/adr-config.adrplus rename to adr-config.adrplus index f09c9e6..83237a2 100644 --- a/docs/adr/adr-config.adrplus +++ b/adr-config.adrplus @@ -1,7 +1,7 @@ { "folderadr": "docs/adr", "migrationpattern": "", - "template": "", + "template": "## Context\n\nDescribe the forces, constraints, and background that make this decision necessary.\n\n## Decision\n\nState the decision that was made.\n\n## Consequences\n\n- **Positive:** \n- **Negative / trade-off:** \n", "prefix": "ADR", "lenseq": 4, "lenversion": 2, diff --git a/docs/adr/ADR0001V01R02-SplitConsolePlusAndPromptPlus.md b/docs/adr/ADR0001V01R02-SplitConsolePlusAndPromptPlus.md index 4635ac7..8f2d321 100644 --- a/docs/adr/ADR0001V01R02-SplitConsolePlusAndPromptPlus.md +++ b/docs/adr/ADR0001V01R02-SplitConsolePlusAndPromptPlus.md @@ -1,6 +1,6 @@ - - + - + - + - + - + - + + True - + + True - $(NoWarn);CS1591 + $(NoWarn);CS1591 ``` -Remova `;CS1591` de `NoWarn` para ver avisos de documentação faltando. +Remove `;CS1591` from `NoWarn` to see warnings about missing documentation. -## 🌐 Links Externos (Opcional) +## 🌐 External Links (Optional) -O arquivo `docs/api/links.json` permite configurar links externos para tipos do .NET Framework: +The `docs/api/links.json` file lets you configure external links for .NET Framework types: ```json { @@ -172,77 +172,77 @@ O arquivo `docs/api/links.json` permite configurar links externos para tipos do } ``` -Adicione novos tipos conforme necessário para melhorar os links da documentação. +Add new types as needed to improve the documentation's links. -## 🚀 Publicando a Documentação +## 🚀 Publishing the Documentation ### GitHub Pages -Para publicar a documentação no GitHub Pages: +To publish the documentation on GitHub Pages: -1. Configure o repositório para usar GitHub Pages -2. Aponte para a branch/pasta que contém os arquivos Markdown -3. A documentação estará disponível em `https://username.github.io/ConsolePlus/` +1. Configure the repository to use GitHub Pages +2. Point it to the branch/folder that contains the Markdown files +3. The documentation will be available at `https://username.github.io/ConsolePlus/` -### ReadTheDocs ou Outras Plataformas +### ReadTheDocs or Other Platforms -Os arquivos Markdown gerados podem ser usados com qualquer plataforma de documentação que suporte Markdown. +The generated Markdown files can be used with any documentation platform that supports Markdown. -## 🐛 Solução de Problemas +## 🐛 Troubleshooting -### Documentação Não Está Sendo Gerada +### Documentation Is Not Being Generated -1. Verifique se `GenerateDocumentationFile` está `True` -2. Confirme que o pacote DefaultDocumentation está instalado -3. Faça um Clean + Rebuild da solução -4. Verifique erros de build no Output +1. Check that `GenerateDocumentationFile` is `True` +2. Confirm that the DefaultDocumentation package is installed +3. Do a Clean + Rebuild of the solution +4. Check for build errors in the Output -### Avisos de Build +### Build Warnings -Se ver avisos relacionados ao DefaultDocumentation, verifique: -- A versão do pacote é compatível com seu .NET SDK -- Todas as propriedades de configuração estão corretas -- Não há conflitos com outros analisadores +If you see warnings related to DefaultDocumentation, check that: +- The package version is compatible with your .NET SDK +- All configuration properties are correct +- There are no conflicts with other analyzers -### Links Quebrados +### Broken Links -Se houver links quebrados na documentação: -- Verifique se os namespaces/tipos referenciados existem -- Atualize `links.json` para tipos externos -- Use `` corretamente nos comentários XML +If there are broken links in the documentation: +- Check that the referenced namespaces/types exist +- Update `links.json` for external types +- Use `` correctly in the XML comments -## 📦 Controle de Versão +## 📦 Version Control -### O que Commitar +### What to Commit -✅ **Commitar**: -- Arquivos de configuração (`ConsolePlus.csproj`) -- Comentários XML no código-fonte -- `links.json` (se usado) +✅ **Commit**: +- Configuration files (`ConsolePlus.csproj`) +- XML comments in the source code +- `links.json` (if used) -❓ **Opcional**: -- Arquivos `.md` gerados em `docs/api/` - - **Commitar**: Para ter histórico e facilitar revisão em PRs - - **Não commitar**: Se preferir gerar sob demanda (adicionar `docs/api/*.md` ao `.gitignore`) +❓ **Optional**: +- Generated `.md` files in `docs/api/` + - **Commit**: To keep a history and make PR review easier + - **Don't commit**: If you prefer to generate them on demand (add `docs/api/*.md` to `.gitignore`) -A escolha depende da preferência da equipe. Commitar permite ver mudanças na documentação em PRs. +The choice depends on the team's preference. Committing lets you see documentation changes in PRs. -## 🤝 Contribuindo +## 🤝 Contributing -Ao fazer um Pull Request que adiciona ou modifica API pública: +When submitting a Pull Request that adds or modifies public API: -1. ✅ Adicione comentários XML completos -2. ✅ Inclua exemplos quando apropriado -3. ✅ Regenere a documentação (build) -4. ✅ Verifique se os arquivos `.md` foram atualizados -5. ✅ Revise a documentação gerada para qualidade +1. ✅ Add complete XML comments +2. ✅ Include examples where appropriate +3. ✅ Regenerate the documentation (build) +4. ✅ Check that the `.md` files were updated +5. ✅ Review the generated documentation for quality -## 📚 Recursos +## 📚 Resources -- [DefaultDocumentation no GitHub](https://github.com/Doraku/DefaultDocumentation) +- [DefaultDocumentation on GitHub](https://github.com/Doraku/DefaultDocumentation) - [XML Documentation Comments (Microsoft)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/) - [Recommended XML Tags (Microsoft)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/recommended-tags) --- -**Última atualização**: Este guia foi criado junto com a configuração inicial do DefaultDocumentation para o ConsolePlus. +**Last updated**: This guide was created together with the initial DefaultDocumentation setup for ConsolePlus. diff --git a/docs/api/ConsolePlus.md b/docs/api/ConsolePlus.md index deabaea..eff3d10 100644 --- a/docs/api/ConsolePlus.md +++ b/docs/api/ConsolePlus.md @@ -3183,7 +3183,7 @@ public static void WriteLines(int steps=1); `steps` [System\.Int32](https://learn.microsoft.com/en-us/dotnet/api/system.int32 'System\.Int32') -Numbers de lines\. +Number of lines\. | Events | | | :--- | :--- | diff --git a/docs/api/Style.md b/docs/api/Style.md index 8895713..5e27c32 100644 --- a/docs/api/Style.md +++ b/docs/api/Style.md @@ -15,6 +15,7 @@ Implements [System\.IEquatable<](https://learn.microsoft.com/en-us/dotnet/api ### Remarks Use the primary constructor to specify explicit colors and an [Overflow](Overflow.md 'ConsolePlusLibrary\.Overflow') strategy, or the +implicit conversion from [Color](Color.md 'ConsolePlusLibrary\.Color') for a quick foreground\-only style with a default background\. ### Constructors @@ -48,6 +49,7 @@ Overflow handling strategy applied when content exceeds the target width\. ### Remarks Use the primary constructor to specify explicit colors and an [Overflow](Overflow.md 'ConsolePlusLibrary\.Overflow') strategy, or the +implicit conversion from [Color](Color.md 'ConsolePlusLibrary\.Color') for a quick foreground\-only style with a default background\. ### Properties diff --git a/src/ConsoleAbstractions/AnsiColorBuilder.cs b/src/ConsoleAbstractions/AnsiColorBuilder.cs index 371722d..07c7318 100644 --- a/src/ConsoleAbstractions/AnsiColorBuilder.cs +++ b/src/ConsoleAbstractions/AnsiColorBuilder.cs @@ -23,10 +23,10 @@ public static IEnumerable Build(ColorSystem system, Color color, bool fore return system switch { - ColorSystem.NoColors => [], // No colors + ColorSystem.NoColors => [], ColorSystem.TrueColor => GetTrueColor(color, foreground), // 24-bit ColorSystem.Standard => GetEightBit(color, foreground), // 8-bit - ColorSystem.FourBit => GetFourBit(color, foreground), // 4-bit + ColorSystem.FourBit => GetFourBit(color, foreground), _ => throw new InvalidOperationException("Could not determine ANSI color oe legacy."), }; } diff --git a/src/ConsoleAbstractions/AnsiConsoleAdapter.cs b/src/ConsoleAbstractions/AnsiConsoleAdapter.cs index e2cf902..e5c6794 100644 --- a/src/ConsoleAbstractions/AnsiConsoleAdapter.cs +++ b/src/ConsoleAbstractions/AnsiConsoleAdapter.cs @@ -41,18 +41,16 @@ public AnsiConsoleAdapter(IProfileReadOnly profile, ILock lockenv, CancellationT _mainToken = mainToken; _lock.Run(() => { - // Change the output and input code page to 65001 (UTF-8) + // Code page 65001 is UTF-8. Console.OutputEncoding = Encoding.UTF8; Console.InputEncoding = Encoding.UTF8; - _currentBuffer = TargetScreen.Primary; //ensure we start with the primary buffer + _currentBuffer = TargetScreen.Primary; _lastWidth = EnvironmentUtil.GetSafeWidth(); _lastHeight = EnvironmentUtil.GetSafeHeight(); _consoleForegroundColor = _profile.DefaultForegroundColor; _consoleBackgroundColor = _profile.DefaultBackgroundColor; - // Start monitoring console size changes StartSizeMonitoring(); - // Ensure cursor is visible by default ShowCursor(); }); @@ -1372,7 +1370,6 @@ public bool SwapBuffer(TargetScreen value) return _lock.Run(() => { ThrowIfDisposed(); - // Switch to TargetBuffer screen switch (value) { case TargetScreen.Primary: @@ -1418,7 +1415,7 @@ private async Task MonitorSizeChanges(CancellationToken cancellationToken) if (currentWidth != pendingWidth || currentHeight != pendingHeight) { - // Tamanho ainda está mudando — reinicia o contador de silêncio + // Size is still changing — reset the silence counter. pendingWidth = currentWidth; pendingHeight = currentHeight; silenceMs = 0; @@ -1430,7 +1427,7 @@ private async Task MonitorSizeChanges(CancellationToken cancellationToken) if (silenceMs >= StabilityThresholdMs) { - // Tamanho estável por StabilityThresholdMs ms — dispara UMA vez + // Size has been stable for StabilityThresholdMs ms — fire the event once. _lastWidth = pendingWidth; _lastHeight = pendingHeight; @@ -1452,11 +1449,11 @@ private async Task MonitorSizeChanges(CancellationToken cancellationToken) } catch (OperationCanceledException) { - // Cancelamento esperado — saída limpa + // Expected cancellation — clean exit. } catch { - // Ignora falhas inesperadas de leitura do console + // Ignore unexpected console read failures. } } diff --git a/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs b/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs index f6d73fc..b78cb7e 100644 --- a/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs +++ b/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs @@ -65,7 +65,7 @@ public NoAnsiConsoleAdapter(IProfileReadOnly profile, ILock lockenv, Cancellatio _mainToken = mainToken; _lock.Run(() => { - // Change the output and input code page to 65001 (UTF-8) + // Code page 65001 is UTF-8. Console.OutputEncoding = Encoding.UTF8; Console.InputEncoding = Encoding.UTF8; @@ -73,10 +73,8 @@ public NoAnsiConsoleAdapter(IProfileReadOnly profile, ILock lockenv, Cancellatio _lastHeight = EnvironmentUtil.GetSafeHeight(); _consoleForegroundColor = _profile.DefaultForegroundColor; _consoleBackgroundColor = _profile.DefaultBackgroundColor; - // Start monitoring console size changes StartSizeMonitoring(); - // Ensure cursor is visible by default - ShowCursor(); + ShowCursor(); }); } @@ -1411,7 +1409,7 @@ private async Task MonitorSizeChanges(CancellationToken cancellationToken) if (currentWidth != pendingWidth || currentHeight != pendingHeight) { - // Tamanho ainda está mudando — reinicia o contador de silêncio + // Size is still changing — reset the silence counter. pendingWidth = currentWidth; pendingHeight = currentHeight; silenceMs = 0; @@ -1423,7 +1421,7 @@ private async Task MonitorSizeChanges(CancellationToken cancellationToken) if (silenceMs >= StabilityThresholdMs) { - // Tamanho estável por StabilityThresholdMs ms — dispara UMA vez + // Size has been stable for StabilityThresholdMs ms — fire the event once. _lastWidth = pendingWidth; _lastHeight = pendingHeight; @@ -1445,11 +1443,11 @@ private async Task MonitorSizeChanges(CancellationToken cancellationToken) } catch (OperationCanceledException) { - // Cancelamento esperado — saída limpa + // Expected cancellation — clean exit. } catch { - // Ignora falhas inesperadas de leitura do console + // Ignore unexpected console read failures. } } diff --git a/src/ConsoleAbstractions/ProfileExtensions.cs b/src/ConsoleAbstractions/ProfileExtensions.cs index e87e792..e950137 100644 --- a/src/ConsoleAbstractions/ProfileExtensions.cs +++ b/src/ConsoleAbstractions/ProfileExtensions.cs @@ -31,7 +31,7 @@ public static void EnrichersCI(this ProfileConsole profile) new Tfs(), new Travis() ]; - //overwrite profile properties (Interactive and SupportsAnsi) based on known CI environments, the first that matches will enrich the profile and break the loop + // First matching CI environment enriches the profile (Interactive, SupportsAnsi) and stops the search. foreach (var itemCI in defaultEnrichers) { if (itemCI.TryEnrich(profile)) diff --git a/src/ConsolePlus.Extra.cs b/src/ConsolePlus.Extra.cs index 57f3eb4..b660a5c 100644 --- a/src/ConsolePlus.Extra.cs +++ b/src/ConsolePlus.Extra.cs @@ -37,7 +37,7 @@ public static void ClearLine(int? row = null, Style? style = null) /// /// Write lines with line terminator /// - /// Numbers de lines. + /// Number of lines. public static void WriteLines(int steps = 1) { _consoledrive.WriteLines(steps); diff --git a/src/ConsolePlus.csproj b/src/ConsolePlus.csproj index 4764c15..b88838d 100644 --- a/src/ConsolePlus.csproj +++ b/src/ConsolePlus.csproj @@ -24,7 +24,7 @@ ConsolePlus.net README.md https://github.com/FRACerqueira/ConsolePlus - 1.0.0-Beta4 + 1.0.0-Beta5 icon.png © 2026 - Fernando Cerqueira True diff --git a/src/Core/AnsiDetector.cs b/src/Core/AnsiDetector.cs index 7f9702b..c97adc0 100644 --- a/src/Core/AnsiDetector.cs +++ b/src/Core/AnsiDetector.cs @@ -127,10 +127,8 @@ internal static bool Detect( private static bool Detect(bool stdError, bool upgrade) { - // Running on Windows? if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // Running under ConEmu? var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI"); if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase)) { @@ -145,7 +143,6 @@ private static bool Detect(bool stdError, bool upgrade) private static bool DetectFromTerm() { - // Check if the terminal is of type ANSI/VT100/xterm compatible. var term = Environment.GetEnvironmentVariable("TERM"); if (!string.IsNullOrWhiteSpace(term)) { @@ -198,11 +195,9 @@ public static bool SupportsAnsi(bool upgrade, bool stdError) { return false; } - // Try enable ANSI support. mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; if (!SetConsoleMode(@out, mode)) { - // Enabling failed. return false; } } diff --git a/src/Core/EnvironmentUtil.cs b/src/Core/EnvironmentUtil.cs index d102471..fefadc1 100644 --- a/src/Core/EnvironmentUtil.cs +++ b/src/Core/EnvironmentUtil.cs @@ -91,65 +91,54 @@ public static int GetSafeHeight(int defaultValue = Constants.DefaultTerminalHeig public static void ResetState(bool isansi, string originalCulture, ConsoleColor originalForecolor, ConsoleColor originalBackcolor, Encoding originalInputEncoding, Encoding originalOutputEncoding) { + // Each restore runs in its own try/catch so a single failure doesn't prevent the others from running. try { - // try restore cursor visibility if it was changed Console.CursorVisible = true; } catch (Exception) { - //skip Exception } try { - // try restore input encoding if it was changed Console.InputEncoding = originalInputEncoding; } catch (Exception) { - //skip Exception } try { - // try restore output encoding if it was changed Console.OutputEncoding = originalOutputEncoding; } catch (Exception) { - //skip Exception } try { if (isansi) { var (left, top) = Console.GetCursorPosition(); - // try Exit alternate screen buffer if it was used Console.Write("\u001b[?1049l"); Console.SetCursorPosition(left, top); } } catch (Exception) { - //skip Exception } try { - // try Restore original culture Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(originalCulture); } catch (Exception) { - //skip Exception } try { - // try Restore original console colors Console.ForegroundColor = originalForecolor; Console.BackgroundColor = originalBackcolor; } catch (Exception) { - //skip Exception } } @@ -429,10 +418,8 @@ public static ColorSystem ColorSystemDetector(bool supportsAnsi) /// True if the environment has terminal support; otherwise, false. public static bool HasTerminalSupport() { - // Check if running on Windows if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // Check for Windows Terminal or ConEmu string? wtSession = Environment.GetEnvironmentVariable("WT_SESSION"); string? conEmu = Environment.GetEnvironmentVariable("ConEmuANSI"); @@ -465,14 +452,12 @@ public static bool HasTerminalSupport() } catch { - //none; } return true; } } else { - // Check terminal type on Unix-like systems string term = Environment.GetEnvironmentVariable("TERM") ?? ""; return AnsiDetector.IsValidTerminal(term); } diff --git a/src/Core/UnicodeDetector.cs b/src/Core/UnicodeDetector.cs index 6a37e54..0f75908 100644 --- a/src/Core/UnicodeDetector.cs +++ b/src/Core/UnicodeDetector.cs @@ -31,16 +31,16 @@ public static bool Detect(TextWriter buffer, AutoDetect unicode) return true; } - // Regras específicas por sistema operacional + // OS-specific detection rules if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // Atalho rápido: encodings Unicode explícitos. + // Fast path: known Unicode code pages (UTF-8 = 65001, UTF-16 LE/BE = 1200/1201, UTF-32 LE/BE = 12000/12001). if (encoding.CodePage is 65001 or 1200 or 1201 or 12000 or 12001) { return true; } - // Terminais modernos normalmente suportam Unicode + // Presence of these env vars indicates a modern terminal (Windows Terminal / ConEmu), which supports Unicode. var wt = Environment.GetEnvironmentVariable("WT_SESSION"); var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI"); @@ -52,13 +52,13 @@ public static bool Detect(TextWriter buffer, AutoDetect unicode) RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - // Em Unix, locale UTF-8 é o melhor indicativo + // On Unix, the locale's UTF-8 declaration is the best indicator of Unicode support. var lang = Environment.GetEnvironmentVariable("LC_ALL") ?? Environment.GetEnvironmentVariable("LC_CTYPE") ?? Environment.GetEnvironmentVariable("LANG") ?? string.Empty; - // TERM=dumb geralmente não oferece recursos completos de terminal + // TERM=dumb means the terminal generally lacks full terminal capabilities. var term = Environment.GetEnvironmentVariable("TERM") ?? string.Empty; if (term.Equals("dumb", StringComparison.OrdinalIgnoreCase)) { @@ -69,14 +69,13 @@ public static bool Detect(TextWriter buffer, AutoDetect unicode) lang.Contains("UTF8", StringComparison.OrdinalIgnoreCase); } - - //Capacidade real de codificação (regra principal) + // Fallback: probe the actual encoding capability directly. var strict = Encoding.GetEncoding( encoding.CodePage, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - // Caracteres de planos diferentes (BMP + suplementar) + // Deliberately spans multiple Unicode planes (BMP + supplementary) to exercise the encoder fully. _ = strict.GetBytes("✓漢🙂"); return true; diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index f3124ae..394f826 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -6,7 +6,6 @@ using System.Resources; using System.Runtime.CompilerServices; -// Specifies the neutral culture for the assembly's resources [assembly: NeutralResourcesLanguage("en-US")] // Grants the headless VirtualTerminal test driver access to internal types (ConsoleWriter, ProfileConsole, IConsolePlus) diff --git a/src/RuntimeEnvironment/Bitbucket.cs b/src/RuntimeEnvironment/Bitbucket.cs index fdd40a3..cf61c91 100644 --- a/src/RuntimeEnvironment/Bitbucket.cs +++ b/src/RuntimeEnvironment/Bitbucket.cs @@ -1,4 +1,4 @@ -// ************************************// *************************************************************************************** +// *************************************************************************************** // MIT LICENCE // Copyright 2020 Patrik Svensson, Phil Scott, Nils Andresen. // https://spectreconsole.net diff --git a/src/Shared/Color.cs b/src/Shared/Color.cs index 368a9da..1e39f9c 100644 --- a/src/Shared/Color.cs +++ b/src/Shared/Color.cs @@ -113,7 +113,6 @@ public Color ExactOrClosest(ColorSystem system) /// public double GetLuminance() { - // Convert RGB from 0-255 to 0-1 double r = R / 255.0; double g = G / 255.0; double b = B / 255.0; @@ -123,7 +122,6 @@ public double GetLuminance() g = g <= 0.03928 ? g / 12.92 : Math.Pow((g + 0.055) / 1.055, 2.4); b = b <= 0.03928 ? b / 12.92 : Math.Pow((b + 0.055) / 1.055, 2.4); - // Calculate relative luminance return 0.2126 * r + 0.7152 * g + 0.0722 * b; } @@ -161,8 +159,6 @@ public double GetContrast(Color other) /// public static Color GetContrastForegroundColor(Color backgroundColor) { - // Use relative luminance to determine if background is dark or light - // If luminance is high (> 0.5), use black; otherwise use white double luminance = backgroundColor.GetLuminance(); return luminance > 0.5 ? Black : White; } @@ -180,10 +176,8 @@ public static Color GetContrastForegroundColor(Color backgroundColor) /// public Color AdjustForegroundColorForContrast(Color backgroundColor, double minimumContrastRatio = 2.5) { - // Calculate current contrast double currentContrast = GetContrast(backgroundColor); - // If contrast is already adequate, return the original color if (currentContrast >= minimumContrastRatio) { return this; diff --git a/src/Shared/EmacsConsoleBuffer.cs b/src/Shared/EmacsConsoleBuffer.cs index d7f8680..d453c9e 100644 --- a/src/Shared/EmacsConsoleBuffer.cs +++ b/src/Shared/EmacsConsoleBuffer.cs @@ -97,33 +97,27 @@ public bool TryAcceptedReadlineConsoleKey(ConsoleKeyInfo keyinfo,int maxlength = switch (keyinfo.Key) { - //toggle input replacement mode. case ConsoleKey.Insert: { _overwritemode = !_overwritemode; } break; - //Emacs keyboard shortcut when when have any text with length > 1 - //Transpose the previous two characters + // Swaps the two characters straddling the cursor (or the last two, if at the end). case ConsoleKey.T when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 1: { TransposeChars(); } break; - //Emacs keyboard shortcut, when when have any text - // Clears the content case ConsoleKey.L when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: { Clear(); } break; - //Emacs keyboard shortcut when when have any text - //Lowers the case of every character from the cursor's position to the end of the current word + // Lowercases only up to the next space, not the whole rest of the buffer. case ConsoleKey.L when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Alt && Length > 0: LowerAfterCursor(); break; - //Emacs keyboard shortcut when when have any text - // Clears the line content before the cursor + // Rebuilds the buffer from the text after the cursor, discarding everything before it. case ConsoleKey.U when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: { string aux = ToForward(); @@ -132,13 +126,11 @@ public bool TryAcceptedReadlineConsoleKey(ConsoleKeyInfo keyinfo,int maxlength = Position = 0; } break; - //Emacs keyboard shortcut when when have any text - //Upper the case of every character from the cursor's position to the end of the current word + // Uppercases only up to the next space, not the whole rest of the buffer. case ConsoleKey.U when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Alt && Length > 0: UpperAfterCursor(); break; - //Emacs keyboard shortcut when when have any text - //Clears the line content after the cursor + // Rebuilds the buffer from the text before the cursor, discarding everything after it. case ConsoleKey.K when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: { string aux = ToBackward(); @@ -147,63 +139,41 @@ public bool TryAcceptedReadlineConsoleKey(ConsoleKeyInfo keyinfo,int maxlength = Position = Length; } break; - //Emacs keyboard shortcut when when have any text - //Clear the word before the cursor case ConsoleKey.W when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: RemoveWordBeforeCursor(); break; - //Emacs keyboard shortcut when when have any text - //Capitalizes the character under the cursor and moves to the end of the word case ConsoleKey.C when _enableEmacsKeys && !_readonly && keyinfo.Modifiers != ConsoleModifiers.Alt && Length > 0: UpperCharMoveEndWord(); break; - //Emacs keyboard shortcut when when have any text - // Clear the word after the cursor case ConsoleKey.D when _enableEmacsKeys && !_readonly &&keyinfo.Modifiers == ConsoleModifiers.Alt && Length > 0: RemoveWordAfterCursor(); break; - //Emacs keyboard shortcut when when have any text - // Moves the cursor forward one word. case ConsoleKey.F when _enableEmacsKeys && keyinfo.Modifiers == ConsoleModifiers.Alt && Length > 0: ForwardWord(); break; - //Emacs keyboard shortcut when when have any text - //Moves the cursor backward one word. case ConsoleKey.B when _enableEmacsKeys && keyinfo.Modifiers == ConsoleModifiers.Alt && Length > 0: BackwardWord(); break; - //Emacs keyboard shortcut when when have any text - //Deletes the previous character (same as backspace). case ConsoleKey.H when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: case ConsoleKey.Backspace when !_readonly && keyinfo.Modifiers == 0 && Length > 0: Backspace(); break; - //Emacs keyboard shortcut when when have any text - //(end) moves the cursor to the line end (equivalent to the key End). case ConsoleKey.E when _enableEmacsKeys && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: case ConsoleKey.End when keyinfo.Modifiers == 0 && Length > 0: ToEnd(); break; - //Emacs keyboard shortcut when when have any text - //Moves the cursor to the line start (equivalent to the key Home). case ConsoleKey.A when _enableEmacsKeys && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: case ConsoleKey.Home when keyinfo.Modifiers == 0 && Length > 0: ToStart(); break; - //Emacs keyboard shortcut when when have any text - //Moves the cursor back one character (equivalent to the key ←). case ConsoleKey.B when _enableEmacsKeys && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: case ConsoleKey.LeftArrow when keyinfo.Modifiers == 0 && Length > 0: Backward(); break; - //Emacs keyboard shortcut when when have any text - //Moves the cursor forward one character (equivalent to the key →). case ConsoleKey.F when _enableEmacsKeys && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: case ConsoleKey.RightArrow when keyinfo.Modifiers == 0 && Length > 0: Forward(); break; - //Emacs keyboard shortcut when when have any text - //Delete the current character (then equivalent to the key Delete). case ConsoleKey.D when _enableEmacsKeys && !_readonly && keyinfo.Modifiers == ConsoleModifiers.Control && Length > 0: case ConsoleKey.Delete when !_readonly && keyinfo.Modifiers == 0 && Length > 0: Delete(); diff --git a/src/Shared/Style.cs b/src/Shared/Style.cs index b79e71e..c44bd82 100644 --- a/src/Shared/Style.cs +++ b/src/Shared/Style.cs @@ -16,6 +16,7 @@ namespace ConsolePlusLibrary /// /// /// Use the primary constructor to specify explicit colors and an strategy, or the + /// implicit conversion from for a quick foreground-only style with a default background. /// /// Foreground used when writing content. /// Background used behind the content. diff --git a/tests/ConsolePlus.Tests/Rendering/BannerWidgetTests.cs b/tests/ConsolePlus.Tests/Rendering/BannerWidgetTests.cs index 4af8523..e8d479f 100644 --- a/tests/ConsolePlus.Tests/Rendering/BannerWidgetTests.cs +++ b/tests/ConsolePlus.Tests/Rendering/BannerWidgetTests.cs @@ -8,9 +8,9 @@ namespace ConsolePlus.Tests.Rendering { - // BannerWidget (Figlet/BannerWidget.cs) — camada 2 (precisa de IConsolePlus para Show(), usa - // VirtualTerminal). Foco no cálculo de largura da borda em GetSegments (largura = maior linha - // renderizada), usando a mesma fonte .flf mínima de FigletFontTests para saída previsível. + // BannerWidget (Figlet/BannerWidget.cs) needs IConsolePlus for Show() and uses VirtualTerminal. + // Focus is on the border-width calculation in GetSegments (width = widest rendered line), using + // the same minimal .flf font as FigletFontTests for predictable output. public class BannerWidgetTests { private static Stream MinimalFont() => new MemoryStream(Encoding.UTF8.GetBytes( diff --git a/tests/ConsolePlus.Tests/Rendering/OverflowTests.cs b/tests/ConsolePlus.Tests/Rendering/OverflowTests.cs index bbd4e41..9128035 100644 --- a/tests/ConsolePlus.Tests/Rendering/OverflowTests.cs +++ b/tests/ConsolePlus.Tests/Rendering/OverflowTests.cs @@ -6,7 +6,7 @@ namespace ConsolePlus.Tests.Rendering { // ConsoleWriter.WriteOutput(Fragment[]) overflow handling (ConsoleAbstractions/ConsoleWriter.cs:98-181). - // Needs a real Width/CursorLeft, hence VirtualTerminal (camada 2) rather than a pure unit test. + // Needs a real Width/CursorLeft, hence VirtualTerminal rather than a pure unit test. public class OverflowTests { private static VirtualTerminal MakeTerminal(bool supportsUnicode) => VirtualTerminal.Create(o => diff --git a/tests/ConsolePlus.Tests/Unit/AnsiColorBuilderTests.cs b/tests/ConsolePlus.Tests/Unit/AnsiColorBuilderTests.cs index 015a3bb..2a30148 100644 --- a/tests/ConsolePlus.Tests/Unit/AnsiColorBuilderTests.cs +++ b/tests/ConsolePlus.Tests/Unit/AnsiColorBuilderTests.cs @@ -6,7 +6,7 @@ namespace ConsolePlus.Tests.Unit { - // AnsiColorBuilder.Build (ConsoleAbstractions/AnsiColorBuilder.cs) — camada 1, sem VirtualTerminal. + // AnsiColorBuilder.Build (ConsoleAbstractions/AnsiColorBuilder.cs) — no VirtualTerminal needed. // Color(255,0,0) matches palette index 9 in both the Standard and FourBit tables // (ColorPalette.cs:128,140 — Color equality only compares R/G/B, ignoring Number). public class AnsiColorBuilderTests diff --git a/tests/ConsolePlus.Tests/Unit/AnsiCommandsTests.cs b/tests/ConsolePlus.Tests/Unit/AnsiCommandsTests.cs index 1b17392..5f23857 100644 --- a/tests/ConsolePlus.Tests/Unit/AnsiCommandsTests.cs +++ b/tests/ConsolePlus.Tests/Unit/AnsiCommandsTests.cs @@ -11,12 +11,12 @@ namespace ConsolePlus.Tests.Unit { - // AnsiCommands (ConsoleAbstractions/AnsiCommands.cs) — camada 1, unidade pura: verifica a - // string ANSI literal emitida por cada comando. Usa um IConsolePlus fake mínimo em vez de - // VirtualTerminal porque AnsiScreenInterpreter só reconhece o subconjunto que a produção - // realmente emite (CUP/EL/ED/SGR/cursor/alt-screen) — vários comandos aqui (setas, tabulação, - // scroll, save/restore cursor) não são interpretados e lançariam NotSupportedException. - // AnsiCommands só toca Out/Error/WriteToErrorOutput em IConsolePlus; todo o resto é stub. + // AnsiCommands (ConsoleAbstractions/AnsiCommands.cs) — pure unit test: verifies the literal ANSI + // string emitted by each command. Uses a minimal fake IConsolePlus instead of VirtualTerminal + // because AnsiScreenInterpreter only recognizes the subset that production actually emits + // (CUP/EL/ED/SGR/cursor/alt-screen) — several commands here (arrows, tab, scroll, save/restore + // cursor) are not interpreted and would throw NotSupportedException. + // AnsiCommands only touches Out/Error/WriteToErrorOutput on IConsolePlus; everything else is a stub. public class AnsiCommandsTests { private sealed class RecordingConsole : IConsolePlus diff --git a/tests/ConsolePlus.Tests/Unit/ColorExtensionsTests.cs b/tests/ConsolePlus.Tests/Unit/ColorExtensionsTests.cs index ebf403a..06a0592 100644 --- a/tests/ConsolePlus.Tests/Unit/ColorExtensionsTests.cs +++ b/tests/ConsolePlus.Tests/Unit/ColorExtensionsTests.cs @@ -4,7 +4,7 @@ namespace ConsolePlus.Tests.Unit { - // ColorExtensions.Weighted (Shared/ColorExtensions.cs) — camada 1, unidade pura. + // ColorExtensions.Weighted (Shared/ColorExtensions.cs) — pure unit test, no dependencies. public class ColorExtensionsTests { private static readonly Color Base = new(200, 100, 50); diff --git a/tests/ConsolePlus.Tests/Unit/ColorJsonConverterTests.cs b/tests/ConsolePlus.Tests/Unit/ColorJsonConverterTests.cs index 0987706..8ec450f 100644 --- a/tests/ConsolePlus.Tests/Unit/ColorJsonConverterTests.cs +++ b/tests/ConsolePlus.Tests/Unit/ColorJsonConverterTests.cs @@ -7,7 +7,7 @@ namespace ConsolePlus.Tests.Unit { - // ColorJsonConverter (Core/ColorJsonConverter.cs) — camada 1, unidade pura. + // ColorJsonConverter (Core/ColorJsonConverter.cs) public class ColorJsonConverterTests { private static JsonSerializerOptions Options() => new() { Converters = { new ColorJsonConverter() } }; diff --git a/tests/ConsolePlus.Tests/Unit/ColorPaletteTests.cs b/tests/ConsolePlus.Tests/Unit/ColorPaletteTests.cs index 1817f9e..8156640 100644 --- a/tests/ConsolePlus.Tests/Unit/ColorPaletteTests.cs +++ b/tests/ConsolePlus.Tests/Unit/ColorPaletteTests.cs @@ -6,8 +6,7 @@ namespace ConsolePlus.Tests.Unit { - // ColorPalette (Core/ColorPalette.cs) — camada 1, unidade pura. Casamento exato/mais próximo - // de cor para as profundidades FourBit/Standard/TrueColor. + // ColorPalette (Core/ColorPalette.cs) public class ColorPaletteTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/ColorTableCssTests.cs b/tests/ConsolePlus.Tests/Unit/ColorTableCssTests.cs index 9047daf..a9afdae 100644 --- a/tests/ConsolePlus.Tests/Unit/ColorTableCssTests.cs +++ b/tests/ConsolePlus.Tests/Unit/ColorTableCssTests.cs @@ -6,8 +6,8 @@ namespace ConsolePlus.Tests.Unit { - // ColorTableCss (Core/ColorTableCss.cs) — camada 1, unidade pura. Tabela de nomes CSS e - // resolução de variantes com peso (ex.: "red500"). + // ColorTableCss (Core/ColorTableCss.cs) — CSS name table and weighted-variant resolution + // (e.g. "red500"). public class ColorTableCssTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/ColorTests.cs b/tests/ConsolePlus.Tests/Unit/ColorTests.cs index b143cd0..891a5c9 100644 --- a/tests/ConsolePlus.Tests/Unit/ColorTests.cs +++ b/tests/ConsolePlus.Tests/Unit/ColorTests.cs @@ -5,8 +5,7 @@ namespace ConsolePlus.Tests.Unit { - // Color (Shared/Color.cs) — camada 1, unidade pura: Blend, luminance/contrast (WCAG), hex - // parsing, numeric/ConsoleColor conversions. + // Color (Shared/Color.cs) public class ColorTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/ConsoleKeyInfoExtensionsTests.cs b/tests/ConsolePlus.Tests/Unit/ConsoleKeyInfoExtensionsTests.cs index f385643..7664d57 100644 --- a/tests/ConsolePlus.Tests/Unit/ConsoleKeyInfoExtensionsTests.cs +++ b/tests/ConsolePlus.Tests/Unit/ConsoleKeyInfoExtensionsTests.cs @@ -6,9 +6,9 @@ namespace ConsolePlus.Tests.Unit { - // ConsoleKeyInfoExtensions (Shared/ConsoleKeyInfoExtensions.cs) — camada 1, unidade pura. - // Decide, para todo controle, se uma tecla "e" Enter/seta/Home/etc; ja tinha 1 bug real conhecido - // (IsPressEnterKey diverge Windows/nao-Windows) que motivou esta suite completa. + // ConsoleKeyInfoExtensions (Shared/ConsoleKeyInfoExtensions.cs) decides, for every control, + // whether a key is Enter/arrow/Home/etc; a real known bug (IsPressEnterKey diverges between + // Windows and non-Windows) motivated this full suite. public class ConsoleKeyInfoExtensionsTests { private static ConsoleKeyInfo K(ConsoleKey key, ConsoleModifiers modifiers = 0, char keyChar = '\0') @@ -66,7 +66,7 @@ public void IsPressSpecialKey_matches_the_given_key_and_modifier_exactly() K(ConsoleKey.X, ConsoleModifiers.Control).IsPressSpecialKey(ConsoleKey.S, ConsoleModifiers.Control).Should().BeFalse(); } - // ---- IsPressEnterKey: OS-dependent (the bug found in Fase 1) ---- + // ---- IsPressEnterKey: OS-dependent ---- [Fact] public void Enter_key_alone_is_accepted_on_any_platform() @@ -88,7 +88,7 @@ public void CtrlJ_is_accepted_as_enter_only_when_emacskeys_is_true() public void Enter_detection_matches_the_current_platforms_semantics() { // No Skippable-fact package here on purpose: this runs the branch matching whatever OS - // executes it, and the project's own CI matrix (Windows + Linux, TEST-PLAN.md section 8) + // executes it, and the project's own CI matrix (Windows + Linux) // exercises both branches across the two runners without adding a test dependency. if (OperatingSystem.IsWindows()) { diff --git a/tests/ConsolePlus.Tests/Unit/CultureExtensionsTests.cs b/tests/ConsolePlus.Tests/Unit/CultureExtensionsTests.cs index 44ccdc9..c43b95a 100644 --- a/tests/ConsolePlus.Tests/Unit/CultureExtensionsTests.cs +++ b/tests/ConsolePlus.Tests/Unit/CultureExtensionsTests.cs @@ -4,7 +4,7 @@ namespace ConsolePlus.Tests.Unit { - // CultureExtensions.ExistsCulture (Shared/CultureExtensions.cs) — camada 1, unidade pura. + // CultureExtensions.ExistsCulture (Shared/CultureExtensions.cs) public class CultureExtensionsTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/CultureInfoJsonConverterTests.cs b/tests/ConsolePlus.Tests/Unit/CultureInfoJsonConverterTests.cs index b83ab7c..1d64b97 100644 --- a/tests/ConsolePlus.Tests/Unit/CultureInfoJsonConverterTests.cs +++ b/tests/ConsolePlus.Tests/Unit/CultureInfoJsonConverterTests.cs @@ -7,7 +7,7 @@ namespace ConsolePlus.Tests.Unit { - // CultureInfoJsonConverter (Core/CultureInfoJsonConverter.cs) — camada 1, unidade pura. + // CultureInfoJsonConverter (Core/CultureInfoJsonConverter.cs) public class CultureInfoJsonConverterTests { private static JsonSerializerOptions Options() => new() { Converters = { new CultureInfoJsonConverter() } }; diff --git a/tests/ConsolePlus.Tests/Unit/DashUtilTests.cs b/tests/ConsolePlus.Tests/Unit/DashUtilTests.cs index 213f253..74dfe8e 100644 --- a/tests/ConsolePlus.Tests/Unit/DashUtilTests.cs +++ b/tests/ConsolePlus.Tests/Unit/DashUtilTests.cs @@ -6,8 +6,8 @@ namespace ConsolePlus.Tests.Unit { - // DashUtil (Core/DashUtil.cs) — camada 1 lógica, mas depende de IConsole.SupportsUnicode, - // por isso usa VirtualTerminal (camada 2 mínima) em vez de um double manual. + // DashUtil (Core/DashUtil.cs) is pure logic but depends on IConsole.SupportsUnicode, so it uses + // a minimal VirtualTerminal instead of a hand-rolled test double. public class DashUtilTests { private static VirtualTerminal Terminal(bool supportsUnicode) => VirtualTerminal.Create(o => o.SupportsUnicode = supportsUnicode); diff --git a/tests/ConsolePlus.Tests/Unit/DisplayLengthTests.cs b/tests/ConsolePlus.Tests/Unit/DisplayLengthTests.cs index 78b9e3f..6ae3214 100644 --- a/tests/ConsolePlus.Tests/Unit/DisplayLengthTests.cs +++ b/tests/ConsolePlus.Tests/Unit/DisplayLengthTests.cs @@ -5,7 +5,7 @@ namespace ConsolePlus.Tests.Unit { - // string.GetDisplayLength (Shared/StringExtensions.cs:59) — camada 1, sem VirtualTerminal. + // string.GetDisplayLength (Shared/StringExtensions.cs:59) public class DisplayLengthTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/EmacsConsoleBufferTests.cs b/tests/ConsolePlus.Tests/Unit/EmacsConsoleBufferTests.cs index 13073f5..4966e82 100644 --- a/tests/ConsolePlus.Tests/Unit/EmacsConsoleBufferTests.cs +++ b/tests/ConsolePlus.Tests/Unit/EmacsConsoleBufferTests.cs @@ -5,8 +5,8 @@ namespace ConsolePlus.Tests.Unit { - // EmacsConsoleBuffer (Shared/EmacsConsoleBuffer.cs) — camada 1, unidade pura. Backs the answer - // buffer of most interactive controls (Input, and others via BaseControlPrompt's answer viewport). + // EmacsConsoleBuffer (Shared/EmacsConsoleBuffer.cs) backs the answer buffer of most interactive + // controls (Input, and others via BaseControlPrompt's answer viewport). // Every Emacs key binding implemented by TryAcceptedReadlineConsoleKey is covered; exact // expected values were confirmed by running each operation and observing the real output // (Ctrl+W/Ctrl+T/Alt+U/Alt+L/Alt+D/Alt+F/Alt+B/Ctrl+C have non-obvious boundary behavior that diff --git a/tests/ConsolePlus.Tests/Unit/EmojiGroupResolutionTests.cs b/tests/ConsolePlus.Tests/Unit/EmojiGroupResolutionTests.cs index 0c87115..e57ddc6 100644 --- a/tests/ConsolePlus.Tests/Unit/EmojiGroupResolutionTests.cs +++ b/tests/ConsolePlus.Tests/Unit/EmojiGroupResolutionTests.cs @@ -4,9 +4,9 @@ namespace ConsolePlus.Tests.Unit { - // Emoji.GetByName group-qualified shortcode resolution (Shared/Emoji.cs:51-78,104-157) — - // camada 1, unidade pura. Deepens EmojiTests.cs (which only covers plain names) with the - // "group/name" form and its compatibility aliases. + // Emoji.GetByName group-qualified shortcode resolution (Shared/Emoji.cs:51-78,104-157). + // Deepens EmojiTests.cs (which only covers plain names) with the "group/name" form and its + // compatibility aliases. public class EmojiGroupResolutionTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/EmojiTests.cs b/tests/ConsolePlus.Tests/Unit/EmojiTests.cs index 37a102f..efc25d3 100644 --- a/tests/ConsolePlus.Tests/Unit/EmojiTests.cs +++ b/tests/ConsolePlus.Tests/Unit/EmojiTests.cs @@ -4,7 +4,7 @@ namespace ConsolePlus.Tests.Unit { - // Emoji.GetByName (Shared/Emoji.cs:51) — camada 1, sem VirtualTerminal. + // Emoji.GetByName (Shared/Emoji.cs:51) public class EmojiTests { [Theory] diff --git a/tests/ConsolePlus.Tests/Unit/FigletFontTests.cs b/tests/ConsolePlus.Tests/Unit/FigletFontTests.cs index 857ff1b..4baf36c 100644 --- a/tests/ConsolePlus.Tests/Unit/FigletFontTests.cs +++ b/tests/ConsolePlus.Tests/Unit/FigletFontTests.cs @@ -9,9 +9,8 @@ namespace ConsolePlus.Tests.Unit { - // FigletFont (Figlet/FigletFont.cs) — camada 1, unidade pura. Parser do formato .flf (FIGlet - // font). Fixtures são fontes .flf mínimas construídas em memória (Stream), sem tocar o recurso - // embutido real nem o disco. + // FigletFont (Figlet/FigletFont.cs) — parser for the .flf (FIGlet font) format. Fixtures are + // minimal .flf fonts built in memory (Stream), without touching the real embedded resource or disk. public class FigletFontTests { // Minimal valid .flf: signature "flf2a", hardblank '$', Height=1 (one line per glyph), diff --git a/tests/ConsolePlus.Tests/Unit/MarkupColorTokenizerTests.cs b/tests/ConsolePlus.Tests/Unit/MarkupColorTokenizerTests.cs index 822c4bc..77af218 100644 --- a/tests/ConsolePlus.Tests/Unit/MarkupColorTokenizerTests.cs +++ b/tests/ConsolePlus.Tests/Unit/MarkupColorTokenizerTests.cs @@ -4,9 +4,9 @@ namespace ConsolePlus.Tests.Unit { - // MarkupColorTokenizer (Core/MarkupColorTokenizer.cs) — camada 1, unidade pura. Parser - // caractere-a-caractere por trás de Fragment.FromText; fault-tolerant por design (markup - // malformado degrada para texto puro em vez de lançar). + // MarkupColorTokenizer (Core/MarkupColorTokenizer.cs) — the character-by-character parser + // behind Fragment.FromText; fault-tolerant by design (malformed markup degrades to plain text + // instead of throwing). public class MarkupColorTokenizerTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/MarkupHelperTests.cs b/tests/ConsolePlus.Tests/Unit/MarkupHelperTests.cs index b54a610..ea0700d 100644 --- a/tests/ConsolePlus.Tests/Unit/MarkupHelperTests.cs +++ b/tests/ConsolePlus.Tests/Unit/MarkupHelperTests.cs @@ -4,8 +4,8 @@ namespace ConsolePlus.Tests.Unit { - // Markup.Escape/Remove/Length (Shared/Markup.cs) — camada 1, unidade pura. Built on top of - // MarkupColorTokenizer (already covered in MarkupColorTokenizerTests). + // Markup.Escape/Remove/Length (Shared/Markup.cs) — built on top of MarkupColorTokenizer + // (already covered in MarkupColorTokenizerTests). public class MarkupHelperTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/MarkupTests.cs b/tests/ConsolePlus.Tests/Unit/MarkupTests.cs index 25cfa3f..bec609f 100644 --- a/tests/ConsolePlus.Tests/Unit/MarkupTests.cs +++ b/tests/ConsolePlus.Tests/Unit/MarkupTests.cs @@ -5,7 +5,7 @@ namespace ConsolePlus.Tests.Unit { - // Fragment.FromText (Shared/Fragment.cs:74) — camada 1, sem VirtualTerminal. + // Fragment.FromText (Shared/Fragment.cs:74) public class MarkupTests { private static readonly Style DefaultStyle = new(Color.White, Color.Black); diff --git a/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs b/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs index ffd6048..1bd2956 100644 --- a/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs +++ b/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs @@ -10,27 +10,26 @@ namespace ConsolePlus.Tests.Unit { - // ProfileExtensions.EnrichersCI (ConsoleAbstractions/ProfileExtensions.cs) — despacha para 14 - // detectores de CI (RuntimeEnvironment/*.cs), cada um IProfileEnrich.TryEnrich checando uma ou - // mais env vars; o primeiro que der match "ganha" e interrompe o loop. + // ProfileExtensions.EnrichersCI (ConsoleAbstractions/ProfileExtensions.cs) dispatches to 14 CI + // detectors (RuntimeEnvironment/*.cs), each an IProfileEnrich.TryEnrich checking one or more env + // vars; the first one that matches "wins" and stops the loop. // - // Blocker de testabilidade: BaseClassCI cacheia Environment.GetEnvironmentVariables() num campo - // private static compartilhado por TODAS as subclasses, populado uma vez e nunca invalidado - // (confirmado por probe empírico). Isso impede testar múltiplos cenários de CI no mesmo processo - // de teste sem resetar o cache manualmente — daqui o uso de reflection (mesmo padrão já usado - // para métodos privados estáticos, ex. MaskEditControl.NormalizeStringMask) para zerar o campo - // antes de cada cenário, sem alterar o código de produção. + // Testability blocker: BaseClassCI caches Environment.GetEnvironmentVariables() in a private + // static field shared by ALL subclasses, populated once and never invalidated (confirmed by + // empirical probe). This prevents testing multiple CI scenarios in the same test process without + // manually resetting the cache — hence the use of reflection (the same pattern already used for + // private static methods, e.g. MaskEditControl.NormalizeStringMask) to zero out the field before + // each scenario, without changing production code. // - // Precisa do GlobalStateCollection (DisableParallelization) porque ConsolePlusExtendsTests - // também toca esse mesmo cache indiretamente (via inicialização real do singleton - // ConsolePlusLibrary.ConsolePlus) — sem isolamento as duas classes competem pelo mesmo campo - // static entre threads paralelas do xUnit (achado real, flake intermitente em CI no macOS, - // 2026-07-25). + // Needs GlobalStateCollection (DisableParallelization) because ConsolePlusExtendsTests also + // touches this same cache indirectly (via real initialization of the ConsolePlusLibrary.ConsolePlus + // singleton) — without isolation the two classes race on the same static field across xUnit's + // parallel threads (real finding, intermittent flake in CI on macOS, 2026-07-25). [Collection(GlobalStateCollection.Name)] public class ProfileExtensionsTests : IDisposable { - // Todas as env vars conhecidas pelos 14 detectores, para isolar cada teste do ambiente real - // (inclusive quando este próprio test suite roda dentro de um CI real). + // All env vars known to the 14 detectors, to isolate each test from the real environment + // (including when this very test suite runs inside a real CI). private static readonly string[] AllKnownCiVars = [ "APPVEYOR", "AZURE_PIPELINES", "bamboo_buildNumber", diff --git a/tests/ConsolePlus.Tests/Unit/StringExtensionsNewLineTests.cs b/tests/ConsolePlus.Tests/Unit/StringExtensionsNewLineTests.cs index 1d84bcc..2ce9807 100644 --- a/tests/ConsolePlus.Tests/Unit/StringExtensionsNewLineTests.cs +++ b/tests/ConsolePlus.Tests/Unit/StringExtensionsNewLineTests.cs @@ -5,9 +5,9 @@ namespace ConsolePlus.Tests.Unit { - // StringExtensions.NormalizeNewLines/SplitLines (Shared/StringExtensions.cs) — camada 1, - // unidade pura, mas com comportamento explicitamente dependente de Environment.NewLine — - // asserções usam Environment.NewLine em vez de "\r\n" hardcoded para funcionar em Windows e Linux. + // StringExtensions.NormalizeNewLines/SplitLines (Shared/StringExtensions.cs) — behavior is + // explicitly dependent on Environment.NewLine, so assertions use Environment.NewLine instead of + // a hardcoded "\r\n" to work on both Windows and Linux. public class StringExtensionsNewLineTests { [Fact] diff --git a/tests/ConsolePlus.Tests/Unit/StyleExtensionsTests.cs b/tests/ConsolePlus.Tests/Unit/StyleExtensionsTests.cs index 086583a..2574b83 100644 --- a/tests/ConsolePlus.Tests/Unit/StyleExtensionsTests.cs +++ b/tests/ConsolePlus.Tests/Unit/StyleExtensionsTests.cs @@ -4,8 +4,8 @@ namespace ConsolePlus.Tests.Unit { - // StyleExtensions (Shared/StyleExtensions.cs) — camada 1, unidade pura. Fluent "with"-style - // helpers used throughout production to derive one Style from another. + // StyleExtensions (Shared/StyleExtensions.cs) — fluent "with"-style helpers used throughout + // production to derive one Style from another. public class StyleExtensionsTests { private static readonly Style Base = new(new Color(1, 2, 3), new Color(4, 5, 6), Overflow.Crop); diff --git a/tests/ConsolePlus.Tests/Unit/TextWriterExtensionsTests.cs b/tests/ConsolePlus.Tests/Unit/TextWriterExtensionsTests.cs index ae2c0d8..6fb7dbc 100644 --- a/tests/ConsolePlus.Tests/Unit/TextWriterExtensionsTests.cs +++ b/tests/ConsolePlus.Tests/Unit/TextWriterExtensionsTests.cs @@ -5,10 +5,10 @@ namespace ConsolePlus.Tests.Unit { - // TextWriterExtensions (Core/TextWriterExtensions.cs) — camada 1, unidade pura: pure reference - // comparison against System.Console.Out/Error. Only reads the singleton (no redirection), so - // it is safe against the "avoid touching the ConsolePlus singleton" rule in TEST-PLAN.md — this - // is System.Console, a different singleton, and only its getter is read, never mutated. + // TextWriterExtensions (Core/TextWriterExtensions.cs) — pure reference comparison against + // System.Console.Out/Error. Only reads the singleton (no redirection), so it is safe against + // the "avoid touching the ConsolePlus singleton" rule — this is System.Console, a different + // singleton, and only its getter is read, never mutated. public class TextWriterExtensionsTests { [Fact] diff --git a/tests/TEST-PLAN.md b/tests/TEST-PLAN.md deleted file mode 100644 index 9e93cfb..0000000 --- a/tests/TEST-PLAN.md +++ /dev/null @@ -1,1269 +0,0 @@ -# Plano de Testes — ConsolePlus + PromptPlus - -> Driver de console **headless** para validar **posicionamento** e **estilo** dos dois produtos, -> rodando em **Windows e Linux**. Decisão de isolamento: **B — isolamento total, driver por projeto**. -> Status: **Fase 1 concluída + cobertura extra de `EmacsConsoleBuffer` (2026-07-22)** — piloto Input+Select -> implementado, mais suíte exaustiva de todas as key bindings emacs (`EmacsConsoleBuffer`, usada pela -> maioria dos controles interativos para editar o buffer de resposta). 69 testes verdes -> (59 ConsolePlus + 10 PromptPlus) em net10.0/net9.0/net8.0 no Windows; Linux cobre-se via CI (seção 8, -> ainda não executado). 1 snapshot Verify estabelecido. Próxima: Fase 2 (rollout dos demais controles). - ---- - -## 1. Objetivo e escopo - -- Testar, de forma automatizada, determinística e cross-platform, as funcionalidades de **ConsolePlus** - (camada de terminal) e **PromptPlus** (controles/widgets). -- Validar **onde** cada glifo é escrito (posição) e **com qual estilo** (fg/bg e, quando modelado, atributos). -- Rodar 100% headless em CI (sem terminal real), idêntico em Windows e Linux. - -**Fora de escopo (por decisão de ROI):** paridade pixel/glifo com terminais reais, timing de animações, -detecção de capacidade dependente de ambiente real. Ver seção 9. - ---- - -## 2. Restrições de testabilidade descobertas no código (base das decisões) - -| Fato | Arquivo | Consequência | -|---|---|---| -| `ConsolePlus` é `static` + `static ctor` (detecta ambiente, cria driver único, troca encoding, inicia `Task` de monitor de tamanho, registra `ProcessExit`/`CancelKeyPress`) | `ConsolePlus.Startup.cs` | Tocar o singleton em teste = estado global; seria serial e sujo. **Evitar.** | -| Saída vai para `Console.Out`/`Console.Error` singletons (`Out => Console.Out`) | `AnsiConsoleAdapter.cs` | Capturar só `Console.Out` não paraleliza. | -| Estilo e cursor são **ANSI inline** (SGR + CSI) quando `SupportsAnsi` | `ConsoleWriter.cs`, `AnsiCommands.cs` | Observáveis se interpretarmos o ANSI. | -| `CursorLeft/Top`, `Width/Height`, `GetCursorPosition()` leem o **`System.Console` real** (defaults em `IOException`) | `EnvironmentUtil.cs`, `AnsiConsoleAdapter.cs` | Capturar `Console.Out` quebra o *read-back* → **inviabiliza testar os controles** que dependem dele. | -| `ReadKey` usa `Console.KeyAvailable`/`Console.ReadKey` (não redirecionável por `SetIn`) | `AnsiConsoleAdapter.cs`, `BaseControlPrompt.WaitKeypress` | Input interativo precisa de **fila de teclas própria**. | -| **Seam de injeção**: `internal sealed PromptPlusControls(IConsole, PromptConfig)`; `BaseControlPrompt(... IConsole console ...)` | `Core/PromptControls.cs`, `Controls/Common/BaseControlPrompt.cs` | Com `InternalsVisibleTo`, dá para instanciar qualquer controle contra um `IConsole` **falso**. | -| PromptPlus já depende de ConsolePlus (`_console = ConsolePlus.Driver`) | `PromptPlus.Startup.cs` | Driver de teste baseado em ConsolePlus **não cria acoplamento novo**. | -| `PromptPlus/src/PromptPlus.csproj:69` referencia ConsolePlus via **`PackageReference` NuGet** (`ConsolePlus.net 0.5.2-Beta`), não via `ProjectReference` para `ConsolePlus/src` | `PromptPlus.csproj` | Um `ProjectReference` de `PromptPlus.Tests` → `PromptPlus/src` traria o ConsolePlus **publicado**, não o build local — o `InternalsVisibleTo` adicionado em `ConsolePlus/src` (seção 6) não alcançaria esse assembly. **Decisão D6, ver seção 12.** | - -**Conclusão:** a estratégia viável é um **terminal virtual em memória que implementa `IConsole`/`IConsolePlus`**, -injetado nos controles, reutilizando o writer real do ConsolePlus. Capturar `Console.Out` sozinho **não serve** -para o PromptPlus. - -> Identidade atual do projeto: `AssemblyName`/`PackageId` = **`ConsolePlus.net`**, `RootNamespace` = `ConsolePlusLibrary`. -> (Artefatos `obj/**/TerminalPlus.*` são resquício de rename antigo — lixo, ignorar/limpar.) - ---- - -## 3. Estratégia escolhida - -**VirtualTerminal**: implementação in-memory de `IConsole` (+ `IConsolePlus`) que **hospeda o `ConsoleWriter`/ -`AnsiCommands`/`AnsiColorBuilder` reais** do ConsolePlus. O ANSI emitido pelo código de produção é **interpretado** -por um `TextWriter` que carimba células `(Rune, Style)` num *grid*. O grid é a fonte da verdade para o *read-back* -de cursor/tamanho — mais fiel e determinístico que o adapter real. - -Camadas de teste (ordem de valor): -1. **Unidade pura** (ConsolePlus): `Fragment.FromText` (markup), `AnsiColorBuilder` (cor→SGR nas 3 profundidades), - Emoji, overflow Crop/Ellipsis, `GetDisplayLength` (largura CJK/emoji). Não precisa de terminal. -2. **Render + estado via VirtualTerminal** (ambos): layout inicial, item selecionado, cursor final, máquinas de estado - (navegação, edição/máscara, validação, filtros, paginação), viewport/ellipsis lateral. -3. **Resize/relayout** (PromptPlus): poucos cenários canônicos via `SizeChanged` + mudança de `Width/Height`. -4. **PTY E2E** (opcional, poucos smokes): ConPTY/pty rodando samples, snapshot da tela — só para fidelidade. - ---- - -## 4. Estrutura de projetos — decisão B (isolamento total) - -Cada projeto de teste é **autossuficiente**: contém sua própria cópia do driver, sem `ProjectReference` para -uma lib de testes compartilhada. Nenhum acoplamento de binário entre `ConsolePlus.Tests` e `PromptPlus.Tests`. - -``` -tests/ - ConsolePlus.Tests/ - Driver/ # driver isolado desta suíte - VirtualTerminal.cs - VirtualScreen.cs - AnsiScreenInterpreter.cs - InputQueue.cs - ScreenAssertions.cs - VirtualTerminalOptions.cs - Unit/ # camada 1 (componentes puros) - Rendering/ # camada 2 (via VirtualTerminal) - ConsolePlus.Tests.csproj # ProjectReference -> ../../ConsolePlus/src/ConsolePlus.csproj - PromptPlus.Tests/ - Driver/ # MESMO driver, cópia isolada - ... - Controls/ # camada 2 por controle - Resize/ # camada 3 - PromptPlus.Tests.csproj # ProjectReference -> ../../PromptPlus/src/PromptPlus.csproj -``` - -### 4.1 Mecanismo de "cópia" do driver — decisão interna a B - -Para honrar "driver por projeto" **sem** manutenção em dobro divergir, o padrão recomendado é -**source linkado a partir de uma fonte única**, compilado dentro de cada suíte (cada assembly de teste -sai self-contained; não há dependência de binário/lib compartilhada — o isolamento de saída de B é mantido): - -``` -tests/_driver-src/*.cs # fonte única do driver (não é projeto) -``` -```xml - - - - -``` - -- **Recomendado (default):** linked source acima → um só código-fonte, duas compilações isoladas. -- **Alternativa (isolamento estrito no nível de fonte):** copiar fisicamente os arquivos em cada `Driver/`. - Custo: duplicação real da peça mais complexa; usar só se a exigência for zero compartilhamento inclusive de fonte. - -> **Decisão em aberto (D1):** confirmar linked-source (default) vs cópia física. Ver seção 12. - -> **Atualização 2026-07-23 — D1 revertida, e a estrutura acima mudou de local.** Depois de fechada, -> D1 (linked-source único em `tests/_driver-src` no topo do repo) foi invalidada: ConsolePlus e -> PromptPlus são dois repositórios GitHub **distintos**, cada um com seu próprio `.git` — só coexistem -> na mesma pasta local por conveniência do desenvolvedor. Um `tests/` no topo (fora de ambos os -> `.git`) não pertence a nenhum dos dois repos publicados: quem clona qualquer um isoladamente não -> recebe pasta de teste nenhuma. D1 foi revertida pra **cópia física** (a alternativa já prevista -> nesta seção): `ConsolePlus/tests/` e `PromptPlus/tests/` agora são as raízes reais, cada uma com sua -> própria cópia de `_driver-src/` e seu próprio projeto de testes — a estrutura ilustrada acima existe, -> mas repetida uma vez dentro de cada repo, não uma vez no topo. Ver -> `docs/testing-driver-maintenance.md` (idêntico nos dois repos) para o procedimento de manter as duas -> cópias em sincronia, e a decisão revista no início da seção 12 (D1). - ---- - -## 5. Componentes do driver (VirtualTerminal) - -- **`VirtualScreen`** — `Cell[Height,Width]` com `Cell(Rune Glyph, Style Style)`, cursor (`Left/Top`), `Current` style; - `Put` (respeita wrap na largura), `EraseToEol`, `EraseDisplay`, `Clear`; leitura `StyleAt/CharAt/TextAt/Snapshot`. -- **`AnsiScreenInterpreter : TextWriter`** — recebe o stream ANSI do writer real e interpreta **apenas o subconjunto - que o ConsolePlus emite** (ver `IAnsiCommands`): `CUP` (posição), `EL` (erase-in-line), `ED` (erase-display), - `SGR ...m` (cor 4/8/24-bit → `Style`), show/hide cursor, enter/exit alt-screen. **Falhar explicitamente** em - sequência desconhecida (evita falso-verde). -- **`VirtualTerminal : IConsole, IConsolePlus`** — `SupportsAnsi => true` (força caminho ANSI determinístico); - `Width/Height` fixos por opção; `GetCursorPosition` lê do grid (read-back fiel); `SetCursorPosition` via - `AnsiCommands` real; `Out => interpreter`; hospeda `ConsoleWriter(this)` de produção; delega markup/estilo/overflow. -- **`InputQueue`** — `Enqueue(ConsoleKey/ConsoleKeyInfo/string)`; alimenta `KeyAvailable`/`ReadKey`/`ReadKeyAsync`. -- **`ScreenAssertions`** — verbos: `CharAt`, `TextAt`, `StyleAt`, `RegionSnapshot`, integração snapshot (Verify). -- **`VirtualTerminalOptions`** — `Width`, `Height`, `ColorDepth`, `SupportsUnicode`, `Interactive`. - -**Esqueletos de código completos no Apêndice A** (este documento é autocontido: dá para implementar sem o histórico da conversa). - ---- - -## 6. InternalsVisibleTo (mudança de produção mínima) - -Nenhuma mudança de comportamento; só atributos: - -- **ConsolePlus** concede IVT a: `ConsolePlus.Tests` **e** `PromptPlus.Tests` - (ambas embedam o driver, que toca `IConsolePlus`, `ConsoleWriter`, `Fragment`, `ProfileConsole`). -- **PromptPlus** concede IVT a: `PromptPlus.Tests` - (para `PromptPlusControls`, `PromptConfig`, `BaseControlPrompt` e controles internos). - ---- - -## 7. Ferramentas - -- **Framework de teste:** xUnit (padrão .NET). Assertions: FluentAssertions. -- **Snapshot:** Verify (golden files do grid completo de um frame — ótimo para "layout inteiro do controle"). -- **Build:** referenciar `src/*.csproj` diretamente (o `.slnx` está quebrado — ver memória de build). -- **TargetFrameworks:** espelhar os do `src` (net8.0; net9.0; net10.0). - ---- - -## 8. CI (Windows + Linux) - -GitHub Actions, matriz `os: [windows-latest, ubuntu-latest]`: -``` -dotnet test tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj -dotnet test tests/PromptPlus.Tests/PromptPlus.Tests.csproj -``` -Camadas 1–3 são headless → resultado idêntico nos dois SOs. Camada 4 (PTY), se existir, roda em job separado -não-bloqueante (tolerante a flakiness/divergência de SO). - -> **Atualização 2026-07-23.** Caminhos reais pós-migração (seção 4): -> `ConsolePlus/tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj` e -> `PromptPlus/tests/PromptPlus.Tests/PromptPlus.Tests.csproj` (cada um dentro do próprio repo). O -> `.slnx` de cada repo passou a incluir o projeto de testes e hoje builda sem erro (a nota "`.slnx` -> está quebrado" da seção 7 não reflete mais o estado atual — carrega e builda normalmente). Ainda -> assim, `ci.yml`/`publish-nuget.yml` **não** dependem do `.slnx`/auto-discovery: cada um chama -> `dotnet build src/*.csproj --configuration Release` (o que é empacotado) e -> `dotnet test tests/*.Tests/*.Tests.csproj --configuration Debug` (onde o driver tem acesso aos -> internos via `ProjectReference`, decisão D6) como dois passos explícitos e independentes — nunca -> `dotnet build`/`dotnet test` sem projeto/config explícitos. Motivo: com os testes agora dentro do -> repo, um `dotnet build --configuration Release` "cego" (que descobre o `.slnx` inteiro) tentava -> compilar também o projeto de testes em Release — e isso quebrava no PromptPlus, porque em Release -> o `PromptPlus/src` resolve o ConsolePlus via `PackageReference` (pacote publicado, sem -> `InternalsVisibleTo` pro driver), não via `ProjectReference` (D6). Validado localmente rodando os -> comandos exatos do CI nos dois repos antes de confiar neles. - ---- - -## 9. O que testar — faixas de ROI - -**ROI alto (fazer):** -- ConsolePlus puro: markup, cor→SGR (3 profundidades), emoji, overflow Crop/Ellipsis, largura CJK/emoji. -- Render inicial de cada controle: prompt, colunas, estilo do item selecionado, posição final do cursor. -- Máquinas de estado: navegação, edição/máscara, validação, filtros do Select, paginação. -- Posicionamento sensível: viewport, ellipsis lateral (`WriteAnswerViewport`, `ViewportSlice`). - -**ROI médio (parcimônia):** -- Resize/relayout do `BaseControlPrompt` (parte mais complexa/bugável): cobrir ~4–5 cenários canônicos - (shrink/grow largura, shrink altura c/ scroll), não a matriz inteira. - -**ROI baixo (NÃO automatizar; manual/visual):** -- Fidelidade em terminal real (WT vs VT100 vs conhost legacy; wrapping/scrollback/emoji largo real). -- Timing de animações (testar estado, não o "quando"). -- Detecção de capacidades (`EnvironmentUtil`/`AnsiDetector`) — testar as funções puras com env-vars mockadas, - nunca o singleton. - ---- - -## 10. API de asserção (posição + estilo) - -```csharp -vt.CursorLeft.Should().Be(12); -vt.TextAt(row: 2, col: 4, len: 5).Should().Be("Nome:"); -vt.StyleAt(row: 2, col: 4).Foreground.Should().Be(Color.Cyan1); -vt.StyleAt(row: 3, col: 0).Should().Be(cfg.SelectedStyle); -vt.Snapshot().Should().MatchVerified(); -``` - ---- - -## 11. Fases de execução (pilot-then-rollout) - -- **Fase 0 — Fundação** ✅ **CONCLUÍDA (2026-07-22)** - - Criar `tests/ConsolePlus.Tests` e `tests/PromptPlus.Tests` (multi-target, ProjectReference a `src/*.csproj`). - - Adicionar os `InternalsVisibleTo` (seção 6) — `ConsolePlus/src/Properties/AssemblyInfo.cs`, `PromptPlus/src/Properties/AssemblyInfo.cs`. - - Corrigir D6 (`PromptPlus/src/PromptPlus.csproj`: `ProjectReference` condicional em Debug para o ConsolePlus local). - - Implementar o driver (fonte única em `_driver-src`, linkado) — `VirtualScreen`/`Cell` + `AnsiScreenInterpreter` + `VirtualTerminal`(+`.Overloads`) + `InputQueue` + `ScreenAssertions`. - - **DoD:** um "hello" via `ConsolePlus`/`VirtualTerminal` grava glifos com estilo correto no grid; build/teste verde em net10.0/net9.0/net8.0 no Windows (`dotnet test` nos dois `*.Tests.csproj`). Linux ainda não validado localmente — cobre-se via CI (seção 8/A.10), pendente de execução. - - **Correções encontradas na implementação (não previstas no Apêndice A original):** `ResetColor()`/`Clear()` precisam setar `ForegroundRgbColor`/`BackgroundRgbColor` (que disparam `ConsoleWriter.ApplyStyle`) em vez de mutar campos privados direto, senão `VirtualScreen.Current` fica dessincronizado do estado real; `IConsolePlus.Writer` exige implementação explícita de interface (`ConsoleWriter IConsolePlus.Writer => _writer;`) porque `ConsoleWriter` é internal e um membro implícito (público) não pode expor um tipo menos acessível (CS0053); `IConsole.Write(string?, Style)`/`WriteLine(string?, Style)` são overloads de 2 argumentos distintas da versão de 3 argumentos com default — precisam de implementação própria, um parâmetro default não as satisfaz. - -- **Fase 1 — Piloto** ✅ **CONCLUÍDA (2026-07-22)** - - ConsolePlus: unidade pura de markup + cor→SGR + overflow (camada 1) — `tests/ConsolePlus.Tests/Unit/*`, - `Rendering/OverflowTests.cs`. 20 testes. - - PromptPlus: `Input` e `Select` (render inicial + navegação + estilo do selecionado + cursor) — - `tests/PromptPlus.Tests/Controls/{SelectControlTests,InputControlTests}.cs`. 8 testes + 1 snapshot. - - **DoD:** ✅ 30 testes verdes (meta era ~15–25) em net10/9/8 no Windows (Linux via CI, seção 8, ainda - não executado); API de asserção validada (`TextAt`/`StyleAt`/`GetCursorPosition`/`Find`); 1 snapshot - Verify estabelecido (`SelectControlSnapshotTests.cs` + `.verified.txt`, idêntico nos 3 TFMs). - - **3 achados de arquitetura documentados na seção 13** (hang em `WaitKeypress` sem tecla terminal; - `InputQueue.Enqueue(Enter)` não confirmava fora do Windows, corrigido; `VirtualTerminal` com - `Width<80`/`Height<10` trava `Run()` para sempre no aviso de terminal pequeno). - -- **Extra (pedido do usuário, 2026-07-22): cobertura de `EmacsConsoleBuffer`** — `ConsolePlus/src/Shared/EmacsConsoleBuffer.cs`, - classe **pública** que sustenta o buffer de edição de resposta (Emacs-style) usado por `Input` e pelo - viewport de resposta de outros controles. Unidade pura (camada 1, sem `VirtualTerminal` — o método - central `TryAcceptedReadlineConsoleKey` recebe `ConsoleKeyInfo` direto). 39 testes em - `tests/ConsolePlus.Tests/Unit/EmacsConsoleBufferTests.cs` cobrindo **todas** as key bindings - implementadas: Ctrl+A/B/C/D/E/F/H/K/L/T/U/W, Alt+D/F/L/U, teclas físicas (Home/End/setas/Delete/ - Backspace/Insert/Tab), Enter (sempre rejeitado), `enableEmacsKeys=false` (desliga atalhos mas mantém - teclas físicas), `readonly`, `maxlength` (achado: retorna `true`/aceito mesmo quando o limite bloqueia - a inserção), `CaseOptions.Uppercase/Lowercase`, função `validate`, `LoadPrintable`, `ToForward`/`ToBackward`. - Valores exatos de operações não-triviais (`Ctrl+W`/`Ctrl+T`/`Alt+U`/`Alt+L`/`Alt+D`/`Alt+F`/`Alt+B`/`Ctrl+C`) - foram confirmados rodando uma sonda descartável e observando o resultado real, não por simulação manual — - `BackwardWord` (Alt+B) tem uma peculiaridade real: só para depois de cruzar **dois** limites de palavra, - então de "hello world foo" (fim) um único Alt+B pula "foo" inteiro e para no início de "world", não em "foo". - -- **Extra (pedido do usuário, 2026-07-22): cobertura de classes fundacionais pequenas do ConsolePlus** - ("base para as classes mais complexas", buscando bugs escondidos). 6 classes cobertas, 145 testes novos, - **1 bug real encontrado e corrigido**: - - `ConsoleKeyInfoExtensions.cs` (53 testes) — todos os `IsPressXKey`/`IsPressXKeyOrEmacs`. Sem bug novo, - mas o teste de `IsPressEnterKey` cobre a divergência Windows/não-Windows já conhecida via branching em - runtime (`OperatingSystem.IsWindows()`) em vez de um pacote de skip — o CI Windows+Linux (seção 8) - exercita os dois ramos naturalmente, cada um no seu runner. - - `DashUtil.cs` (27 testes) — `GetBorderUp`/`GetBorderDown` para as 10 combinações de `DashOptions` × unicode/ascii. Sem bug. - - `StyleExtensions.cs` (6 testes) — `Colors`/`ForeGround`/`Background`/`Overflow`. Sem bug. - - `ColorExtensions.cs` + `Color.cs` (22 testes) — `Weighted`, `Blend`, `GetLuminance`, `GetContrast`, - `ToHex`/`FromHex`/`TryFromHex`, `FromInt32`, `FromConsoleColor`↔`ToConsoleColor`. - **🐛 Bug real corrigido**: `Color.GetInvertedColor()` (`Shared/Color.cs:78-81`) comparava - `GetLuminance() < 140`, mas `GetLuminance()` (fórmula WCAG) sempre retorna um valor em **[0,1]** — a - condição era sempre verdadeira, então o método **sempre retornava `White`**, para qualquer cor - (confirmado: `White.GetInvertedColor()` também dava `White`, quando deveria dar `Black`). O método - irmão `GetContrastForegroundColor` já usava o threshold correto (`0.5`) na mesma escala. Corrigido para - `GetLuminance() < 0.5`; só usado em samples/docs, nenhum controle de produção dependia do valor errado. - - `ColorPalette.cs` + `ColorTableCss.cs` (20 testes) — `ExactOrClosest` (match exato + "closest" via - fórmula redmean) nas 3 profundidades; tabela CSS (`TryGetColor`/`TryGetName`/`TryGetWeightedColor`, - incluindo nomes com peso tipo `"red500"`). Sem bug. - - `MarkupColorTokenizer.cs` (17 testes) — parser char-a-char por trás de `Fragment.FromText`: tags - abertas/fechadas, `[[`/`]]` escapados, tags malformadas (`[/x]`, `[tag` sem fechar, `[tag[next]`), - posição de token. Sem bug — o parser é mais fault-tolerant do que aparenta (degrada pra texto puro - consistentemente em vez de lançar). - - Total após este lote: **204 testes** em `ConsolePlus.Tests` (+ 10 em `PromptPlus.Tests`) verdes em - net10.0/net9.0/net8.0 no Windows. - -- **2º lote de classes fundacionais (pedido do usuário, 2026-07-22, "continua com mais"): 57 testes novos, - 2º bug real encontrado e corrigido**: - - `Markup.cs` (11 testes) — `Escape`/`Remove`/`Length` sobre o `MarkupColorTokenizer` já testado; - round-trip `Remove(Escape(x)) == x` confirmado. Sem bug. - - `ColorJsonConverter.cs` (6 testes) + `CultureInfoJsonConverter.cs` (7 testes) + `CultureExtensions.cs` - (2 testes) — descobertos ao explorar vizinhos do `Markup.cs`. - **🐛 2º bug real corrigido**: `ColorJsonConverter.Read` (`Core/ColorJsonConverter.cs:34-41`) só - capturava `ArgumentException`, mas hex com dígitos inválidos (ex.: `"#GGGGGG"`) faz `Color.FromHex` - lançar `FormatException` (via `byte.Parse`) — não capturado, vazava cru pro consumidor em vez do - `JsonException` com mensagem legível que o método promete. Corrigido para - `catch (Exception ex) when (ex is ArgumentException or FormatException)`. - - `StringExtensions.NormalizeNewLines`/`SplitLines` (9 testes) — comportamento cross-platform via - `Environment.NewLine` (não hardcoded `"\r\n"`, pra funcionar em Windows e Linux). Achado não-óbvio - documentado num teste: `\r` solto (estilo Mac antigo) é apenas removido, não convertido — a quebra de - linha se perde. Sem bug (comportamento documentado no comentário do código). - - `AnsiCommands.cs` (22 testes) — formatação literal de CSI/SGR pra **todos** os comandos (não só o - subconjunto que `AnsiScreenInterpreter` reconhece), usando um `IConsolePlus` fake mínimo (só - `Out`/`Error`/`WriteToErrorOutput` reais, resto `NotImplementedException`) em vez de `VirtualTerminal`, - já que comandos como setas/tabulação/scroll/save-restore-cursor não são interpretados pelo driver e - lançariam `NotSupportedException`. Sem bug de produção — 1 erro no próprio teste (`"\x1bD"` em C# é - interpretado como um único char Unicode U+01BD, já que `\x` consome até 4 dígitos hex e `D` é hex - válido; corrigido para `"\x1b" + "D"`). - - Total após os dois lotes extra: **261 testes** em `ConsolePlus.Tests` (+ 10 em `PromptPlus.Tests`) - verdes em net10.0/net9.0/net8.0 no Windows. - -- **3º lote de classes fundacionais (pedido do usuário, 2026-07-22): 16 testes novos, sem bug**: - - `Helper.cs` (15 testes) — `MainToken`/`ExitCode`/`LastException` são estado global mutável - compartilhado por todo o assembly ConsolePlus. Cada teste salva o valor original e restaura em - `finally`, para não vazar estado pra outra classe de teste rodando em paralelo (xUnit paraleliza - entre classes por padrão). - - `TextWriterExtensions.cs` (4 testes) — `IsStandardOut`/`IsStandardError` são comparação de - referência contra `System.Console.Out`/`Error`; só *leem* o singleton real (nunca redirecionam), - então não violam a regra do plano de "evitar o singleton" — essa é uma leitura inofensiva. - - `Emoji.cs` — resolução de shortcode qualificado por grupo (`"activities/balloon"`, - `Shared/Emoji.cs:51-78,104-157`), aprofundando `EmojiTests.cs` (que só cobria nomes simples): - grupo válido resolve, case-insensitive, grupo desconhecido rejeita (não cai pra busca de texto - livre), nome desconhecido dentro de grupo válido retorna vazio, alias de grupo sem "And" - (`"peoplebody/thumbs_up"` via `PeopleAndBody`) também resolve. 8 testes, sem bug. - - Total após os três lotes extra: **277 testes** em `ConsolePlus.Tests` (+ 10 em `PromptPlus.Tests`) - verdes em net10.0/net9.0/net8.0 no Windows. - -- **Classes fundacionais/de apoio do PromptPlus (pedido do usuário, 2026-07-22, "mesmos critérios"): - 45 testes novos, 3º e 4º bugs reais encontrados e corrigidos**: - - `Paginator` (`Controls/Common/Paginator.cs`, 20 testes) — motor de paginação reusado por - ~10 controles (Select, MultiSelect, Tree, MultiTree, MultiTasks, MultiFile, ChartBar, Table, - MultiTable, FileExec, Calendar). **🐛 3º bug real corrigido**: `UpdateCollection()` não resetava - `SelectedPage` antes de chamar `FirstItem()` — recarregar uma coleção **mais curta** enquanto - parado numa página posterior deixava o paginador sem seleção (`SelectedIndex=-1`) mesmo havendo - itens válidos na nova coleção. O método irmão `UpdateFilter()` já resetava `SelectedPage=0` - corretamente; `UpdateCollection()` só esqueceu. **Afeta produção de verdade**: ~9 chamadas em - `MultiSelectControl` e 3 em `MultiTableControl` (features "mostrar só selecionados"/refresh). - Corrigido adicionando `SelectedIndex=-1; SelectedPage=0;` no início do método, espelhando - `UpdateFilter`. Também documentado (sem ser bug): `LastItem()`/`FirstItem()` chamados diretamente - são relativos à página ATUAL, não à coleção inteira — só funcionam com semântica de - "primeiro/último absoluto" quando chamados via `End()`/`Home()`, que navegam pra página certa - primeiro; e `NextItem()`/`PreviousItem()` não dão wrap-around nos extremos absolutos da coleção - (só `NextPage`/`PreviousPage` dão wrap, via `%PageCount`). - - `Optional` (`Controls/Common/Optional.cs`, 13 testes). **🐛 4º bug real corrigido (latente)**: - `operator ==(T left, Optional right)` comparava `left` contra `right.Value` sem checar - `right.HasValue` — `0 == Optional.Empty()` retornava `True` (deveria ser `False`). Confirmado - via investigação que nenhum código de produção usa esse operador hoje (só `.HasValue`/`.Value` - diretamente); risco latente, não bug visível ainda. Corrigido para checar `HasValue` primeiro. - - `HotKey` (`Shared/Common/HotKey.cs`, 12 testes). **🐛 5º bug real corrigido (latente)**: - `KeyInfo` fazia `(char)Key` sem qualquer filtro — funcionava certo só por coincidência pra - A-Z/D0-D9/Escape/Tab/Backspace/Enter (valores do enum `ConsoleKey` batem com códigos ASCII reais - nesses casos), mas produzia `KeyChar` sem sentido pra qualquer outra tecla (F1→`'p'`, setas, - Home/End/PageUp/Down, teclas Oem, etc.). Uma correção inicial por faixa numérica (`>32 and <127`) - não bastou — muitas teclas especiais caem dentro dessa faixa por coincidência (setas 37-40, - Home/End 35-36, F1-F14 112-125 se sobrepõem à banda imprimível). Corrigido com allowlist explícita - (Backspace/Tab/Enter/Escape/Spacebar/D0-D9/A-Z), retornando `'\0'` pra tudo fora dela. `KeyInfo` - não é usado em nenhum lugar da produção hoje (`grep` confirmou zero chamadas) — risco latente. - - Total: **332 testes** (277 `ConsolePlus.Tests` + 55 `PromptPlus.Tests`) verdes em - net10.0/net9.0/net8.0 no Windows. **5 bugs reais de produção** encontrados e corrigidos até agora - nesta frente de testes fundacionais (2 no ConsolePlus, 3 no PromptPlus). - - `BufferScreen`/`BufferState`/`LineScreen` (`Controls/Common/{BufferScreen,BufferState,LineScreen}.cs`, - 19 testes) — motor de diff de renderização usado por **todo** controle via - `BaseControlPrompt.RenderBuffer` (`UpdateBufferDiff`, `PhysicalLineCount` pro cálculo de - reflow no resize). Sem bug — matemática de `PhysicalLineCount` (clipping pela largura de render - anterior + wrap na largura nova) e as regras de diff (`UpdateBufferDiff` só reporta linhas - mudadas ou genuinamente novas, nunca remoções — isso é responsabilidade separada do chamador) - conferem exatamente com o esperado, confirmado por sonda antes de travar as asserções. - - Total final: **351 testes** (277 `ConsolePlus.Tests` + 74 `PromptPlus.Tests`) verdes em - net10.0/net9.0/net8.0 no Windows. - -- **3º lote de classes de apoio do PromptPlus (pedido do usuário, 2026-07-22, "siga a ordem até o - final, pulando AnsiDetector/UnicodeDetector"): 79 testes novos, 6º bug real (duplicado) + 1 - fragilidade latente corrigida + 1 mudança de arquitetura (nova dependência)**: - - `PromptPlus/src/Core/ColorJsonConverter.cs` (5 testes). **🐛 6º bug real corrigido**: cópia exata - do `ColorJsonConverter` do ConsolePlus, com o **mesmo bug** (`catch(ArgumentException)` não pega - `FormatException` de `Color.FromHex`) — mas é uma classe compilada num assembly diferente, então - a correção do ConsolePlus não valia pra essa. `CultureInfoJsonConverter.cs` (PromptPlus) também é - cópia exata, mas já estava correta (sem teste extra, coberto pela suíte do ConsolePlus). - - `MaskEditBuffer`/`MaskElement` (`Controls/MaskEdit/*.cs`, 19 testes) — máquina de estado de - edição de máscara (string/número/moeda/data-hora), base de toda a família `MaskEdit*Control`. - Fixtures construídas via reflection chamando os métodos `private static` - `NormalizeStringMask`/`NormalizeNumberMask` reais de `MaskEditControl` (IVT não relaxa - `private`), garantindo que o layout dos elementos bate exatamente com a produção. Comportamento - de entrada numérica "shift-left" (dígito novo empurra os anteriores, backspace remove o - mais-recente-digitado) e navegação (`Tab`/setas/Home/End/Emacs) confirmados por sonda antes de - travar as asserções. **Fragilidade latente corrigida**: o construtor indexava - `_charElements[_decimalposition]` sem checar limites — uma máscara numérica sem separador - decimal deixava `CursorPosition=-1` e lançava `KeyNotFoundException` na primeira tecla. - Inalcançável hoje (`MaskEditControl.SetNumberFormat` sempre inclui um `.`, mesmo pra inteiros - com 0 casas decimais), mas adicionada guarda no construtor (`ArgumentException` imediato) para - não depender desse invariante silenciosamente pra sempre. - - `ComponentStyles.cs` — acabou sendo só um **enum** (nomes de região de estilo), sem lógica; nada - a testar (suposição inicial errada de que seria uma classe de dicionário). - - `HotKeyJsonConverter.cs` (10 testes) — JSON custom (objeto `{key,alt,ctrl,shift}`), único do - PromptPlus. Sem bug — validação de token/propriedade já bem escrita. - - `SpinnerBase.cs` (6 testes, via double de teste já que é abstrata) + catálogo `SpinnerBase.Known` - (1 teste reflexivo cobrindo todas as entradas) — ciclo de frames circular, base de spinners de - Task/Progress. Sem bug. - - `FileHistory.cs` (15 testes) — **mudança de arquitetura, pedida explicitamente pelo usuário**: - a classe escrevia direto em `%USERPROFILE%\PromptPlus.History\` sem nenhum seam de injeção. - Adicionado `System.IO.Abstractions` (produção) + `System.IO.Abstractions.TestingHelpers` (testes) - — `FileHistory.FileSystem` agora é um `IFileSystem` estático substituível (`internal`, default - `new FileSystem()` real), testes usam `MockFileSystem` (puramente em memória, idêntico em - Windows/Linux) salvo/restaurado via `IDisposable` por teste, mesmo padrão de - `HelperTests`. `AddHistory` (puro, sem I/O) testado direto. Sem bug — dedup/expiração/ordenação/ - corte por `maxitem` todos corretos. - - Total após este lote: **407 testes** (277 `ConsolePlus.Tests` + 130 `PromptPlus.Tests`) verdes em - net10.0/net9.0/net8.0 no Windows. **6 bugs reais de produção** corrigidos até agora (3 no - ConsolePlus, 3 diretos + 1 duplicado no PromptPlus — GetInvertedColor, ColorJsonConverter(x2), - Paginator.UpdateCollection, Optional, HotKey.KeyInfo) + 1 fragilidade latente adicional - (MaskEditBuffer). (Correção de aritmética: o total/contagem de `PromptPlus.Tests` reportado - antes, 153, estava errado — o valor correto, confirmado por execução, é 130.) - -- **4º lote — classes finais do ConsolePlus (pedido do usuário, 2026-07-22, mesma ordem "siga até o - final, pulando AnsiDetector/UnicodeDetector"): 36 testes novos, 7º bug real corrigido**: - - `Figlet/FigletFont.cs` (10 testes, `Unit/FigletFontTests.cs`) — camada 1, unidade pura. Parser do - formato `.flf` (FIGlet font). Fixture mínima construída em memória (`MemoryStream`), sem tocar o - recurso embutido real nem o disco: header, `CommentLines`, substituição de hardblank, - concatenação de glyphs por caractere, `ToAsciiArtMarkup` (fragmentos de conteúdo + quebra de - linha), assinatura inválida (`InvalidDataException`), caminho nulo/vazio - (`ArgumentNullException`), arquivo inexistente (`FileNotFoundException`, caminho cross-platform - via `Path.GetTempPath()`), construtor sem parâmetros carregando a fonte `Standard` embutida. Sem - bug. - - `Figlet/BannerWidget.cs` (4 testes, `Rendering/BannerWidgetTests.cs`) — camada 2 (usa - `IConsolePlus`/`VirtualTerminal` via `Show()`). `Show()` escreve o glyph renderizado na grade; - `Border(DashOptions)` adiciona linha superior/inferior com a largura da linha mais larga - renderizada; sem borda por padrão só escreve a linha do glyph; texto vazio não escreve nada. - Reutiliza a mesma fonte `.flf` mínima de `FigletFontTests`. **Erro de teste autocorrigido antes - de rodar**: a primeira versão comparava `vt.TextAt(...)` com `string.Empty`, mas células não - tocadas da grade renderizam como espaço (`' '`), nunca como string vazia — corrigido para - `.Trim().Should().BeEmpty()`, mesmo padrão de lição já registrada pra `VirtualScreen.Snapshot()`. - Sem bug. - - `ConsoleAbstractions/ProfileExtensions.cs` (22 testes, `Unit/ProfileExtensionsTests.cs`) — - `EnrichersCI()` despacha pra 14 detectores de CI (`RuntimeEnvironment/*.cs`: AppVeyor, - AzurePipelines, Bamboo, Bitbucket, Bitrise, Continua, GitHub, GitLab, GoCD, Jenkins, MyGet, - TeamCity, Tfs, Travis), o primeiro que der match nas env vars conhecidas "ganha" e para o loop. - **Bloqueio de testabilidade confirmado por sonda empírica**: `BaseClassCI` cacheia - `Environment.GetEnvironmentVariables()` num campo `private static` compartilhado por TODAS as - subclasses, populado uma vez e nunca invalidado — a sonda provou que, depois do primeiro match - (ex. GitHub), trocar a env var real e chamar outro detector (GitLab) ainda retornava o resultado - do primeiro (GitHub), por causa do cache obsoleto. Resolvido via reflection nos testes - (`BaseClassCI._environmentVariables` zerado antes de cada cenário — mesmo padrão já usado pra - métodos `private static`), sem alterar código de produção. Cada teste também limpa as 14 env vars - conhecidas antes/depois (`IDisposable`) pra não depender do ambiente real onde a suíte rodar. - **🐛 7º bug real corrigido**: `Continua.cs` era o único dos 14 detectores que não setava - `profile.ChangedColorDepth = true` ao forçar `ColorDepth = FourBit`. Como - `EnvironmentUtil.cs:315` faz `if (!profile.ChangedColorDepth) { profile.ColorDepth = colordetect; }` - logo depois de `EnrichersCI()`, o `FourBit` forçado pelo Continua era silenciosamente sobrescrito - pela auto-detecção — a detecção de CI "funcionava" (retornava `true`, `Interactive=false`) mas o - `ColorDepth` correto nunca chegava a valer. Corrigido para setar `ChangedColorDepth = true` como - os outros 13, com teste de regressão dedicado. - - Total após este lote: **443 testes** (313 `ConsolePlus.Tests` + 130 `PromptPlus.Tests`) verdes em - net10.0/net9.0/net8.0 no Windows. **7 bugs reais de produção** corrigidos até agora (3 no - ConsolePlus — GetInvertedColor, ColorJsonConverter, Continua.ChangedColorDepth; 4 no PromptPlus — - ColorJsonConverter (duplicado do bug do ConsolePlus), Paginator.UpdateCollection, Optional, - HotKey.KeyInfo) + 1 fragilidade latente adicional (MaskEditBuffer). Lista aprovada pelo usuário - ("siga a ordem até o final, pulando AnsiDetector/UnicodeDetector") esgotada. - -- **Fase 2 — Rollout** - - Cobrir os demais controles/widgets em ondas, reusando o padrão do piloto (catalog-then-fix). - - **DoD:** todos os controles com pelo menos render inicial + fluxo principal. - -- **Fase 3 — Resize (driver estendido em 2026-07-24)** — continuação da Fase 2, cujo tracker - detalhado (`FASE2-CONTROLS-PLAN.md`) está congelado em 2026-07-23; os achados abaixo (pós-Fase 2) - não estão lá, só aqui. - - `VirtualTerminal.RaiseResize` antes só disparava o evento `SizeChanged`, sem redimensionar o - `VirtualScreen` de fato (`Width`/`Height` eram `{ get; }`, sem setter) — o subsistema de resize - da `BaseControlPrompt` era literalmente intestável. Estendido: `VirtualScreen.Resize(w,h)` real - (realoca a grade preservando a região de interseção nas mesmas coordenadas — sem reflow de - texto, igual a um terminal real — e blanks nas células recém-expostas; cursor é clampado se - caiu fora); `RaiseResize` chama esse resize ANTES de disparar o evento. - - Primeiro teste real de resize (`PromptPlus/tests/PromptPlus.Tests/Controls/ - ResizeRelayoutTests.cs`) achou um **bug real de produção só visível com resize de verdade**: - `InputControl.TryResult` setava `_updatePosAnswerBuffer = true` antes de ler a próxima tecla e - só resetava pra `false` no caminho de tecla normal — um `break` por `press.IsResize` saía do - loop com a flag ainda `true`, e a passagem de render seguinte (o relayout de resize) recarregava - `_inputdata` a partir de `_lastinput` + `ToHome()`, resetando o cursor de edição pro início do - texto mesmo sem o usuário ter feito nada. Corrigido movendo o reset pra antes do `if - (press.IsResize || press.IsCancelled)`. - - **Achado não perseguido nesta sessão**: o mesmo padrão (`press.IsResize || press.IsCancelled` - seguido de `break`) existe em outros 18 controles — candidato natural pra uma auditoria futura - agora que resize é testável, mas fora do escopo desta rodada. - -- **Fase 4 — PTY E2E (opcional/baixo ROI)** - - Poucos smokes rodando samples sob ConPTY/pty. - ---- - -## 12. Decisões (fechadas em 2026-07-22; D1 revista em 2026-07-23 — ver nota) - -- **D1 (mecanismo do driver em B):** ✅ **linked-source único** (`tests/_driver-src/*.cs`, `` em cada `*.Tests.csproj`). Confirmado: nenhum projeto de teste ou padrão de link-source existe hoje no repo (terreno limpo, sem conflito). - → **REVISTA em 2026-07-23**: linked-source presumia um único repo; como ConsolePlus/PromptPlus são - dois repos GitHub distintos, D1 foi trocada pra **cópia física** (`ConsolePlus/tests/_driver-src` e - `PromptPlus/tests/_driver-src`, cópias independentes, cada uma versionada no próprio repo). Ver - nota na seção 4 e `docs/testing-driver-maintenance.md`. Alternativas rejeitadas na hora (pacote - NuGet próprio, drivers independentes do zero) documentadas no mesmo arquivo. -- **D2:** ✅ **xUnit + Verify** (não TUnit) — mantém os esqueletos do Apêndice A como estão. -- **D3:** ✅ Confirmado por leitura direta: `ConsolePlus/src/ConsolePlus.csproj:4` e `PromptPlus/src/PromptPlus.csproj:3` usam ambos `net10.0;net9.0;net8.0`. Espelhar exatamente essa lista/ordem nos `*.Tests.csproj`. -- **D4:** ✅ **Só cor (fg/bg), sem atributos.** Verificado em código: `Style` (`ConsolePlus/src/Shared/Style.cs:23-38`) só tem `Foreground`/`Background`/`OverflowStrategy`; `ConsoleWriter.ApplyStyle` (`ConsoleAbstractions/ConsoleWriter.cs:257-268`) só emite SGR de `AnsiColorBuilder` (cor). Produção não emite bold/underline/etc hoje — nada a modelar no grid. Os comentários `// TODO Fase 2: 4-bit / 256` e a nota de D4 no Apêndice A.3/A.5 ficam confirmados como "não aplicável", não como pendência. -- **D5:** ✅ Piloto = **`Input` + `Select`**, confirmado, **com correção de API** em relação ao Apêndice A.9 (ver nota abaixo): `PromptPlusControls.Input(...)`/`.Select(...)` retornam interfaces **públicas** (`IInputControl`, `ISelectControl` — `PromptPlus/src/Core/PromptControls.cs:167-188`), não é preciso `dynamic`/cast para classe concreta interna. Execução é `Run(CancellationToken token = default)` (não `Run()` sem argumento) — `IInputControl.cs:173`, `ISelectControl.cs:37` — retornando `ResultPrompt` (`Shared/Common/ResultPrompt.cs:16-38`) cujas propriedades são **`Content`** (não `Value`) e **`IsAborted`**. -- **D6 (nova, descoberta na verificação — divergência de arquitetura):** `PromptPlus/src/PromptPlus.csproj:69` traz o ConsolePlus via `PackageReference` (NuGet publicado), não `ProjectReference`. Isso quebraria o `InternalsVisibleTo` planejado (seção 6) para `PromptPlus.Tests`, pois o assembly resolvido seria o pacote publicado, não o build local de `ConsolePlus/src`. - → **Decisão:** `ProjectReference` condicional por `Configuration` em `PromptPlus/src/PromptPlus.csproj` — `PackageReference` em Release (empacotamento normal), `ProjectReference` para `../../ConsolePlus/src/ConsolePlus.csproj` em Debug (testes/dev). Este `.csproj` **já usa esse padrão** (`ItemGroup Condition="'$(Configuration)' == 'Release' ..."` na linha 62 para `DefaultDocumentation`), então a mudança segue um padrão já existente no arquivo, não introduz um novo. Ação concreta na Fase 0 (não aplicada ainda): - ```xml - - - - - - - ``` - `dotnet test` roda em `Debug` por padrão, então `PromptPlus.Tests` passa a puxar o ConsolePlus local automaticamente sem precisar de `-c` explícito. - ---- - -## 13. Riscos e mitigações - -- **"Testar o espelho"** (driver e lib compartilharem lógica de largura): o interpretador ANSI é independente do writer; - largura/overflow são cobertos na camada 1 (unidade pura) contra valores fixos, não contra o próprio driver. -- **Escopo do interpretador:** implementar só o subconjunto emitido; falhar em sequência desconhecida. -- **Divergência do driver duplicado (se D1 = cópia física):** mitigar com linked-source. -- **InternalsVisibleTo:** acopla testes a internos — aceitável em libs deste tipo. -- **Trava (hang) em testes de `Input`/`Select` (achado na Fase 1, 2026-07-22):** `WaitKeypress` - (`PromptPlus/src/Controls/Common/BaseControlPrompt.cs:745-758`) faz spin-wait em `console.KeyAvailable` - até haver tecla ou `token` cancelar — **não lança excessão nem retorna se a fila esvaziar antes de uma - tecla terminal**. Regra obrigatória para todo teste de controle: (1) sempre enfileirar uma tecla terminal - (Enter para confirmar, Escape para abortar) como última tecla da sequência; (2) sempre passar um - `CancellationToken` com timeout curto (ex.: 2s) em `Run(token)` como rede de segurança. -- **`InputQueue.Enqueue(ConsoleKey.Enter)` não confirmava nada fora do Windows (achado e corrigido na - Fase 1, 2026-07-22):** `IsPressEnterKey` (`ConsolePlus/src/Shared/ConsoleKeyInfoExtensions.cs:42-48`) - compara `.Key` no Windows mas **`.KeyChar` (13/10)** nos demais SOs. O driver original da Fase 0 criava - `ConsoleKeyInfo` com `KeyChar='\0'` para qualquer `ConsoleKey`, quebrando a promessa "idêntico em - Windows e Linux" especificamente para Enter. Corrigido em `tests/_driver-src/InputQueue.cs` - (`DefaultCharFor`): `Enqueue(ConsoleKey.Enter/Tab/Backspace/Escape)` agora usa o `KeyChar` real que um - console de verdade reportaria para essas teclas. Navegação (setas, Home/End) não é afetada — essas - comparam só `.Key`. -- **`VirtualTerminal` com `Width`/`Height` abaixo do mínimo trava `Run()` para sempre (achado na Fase 1, - investigado com o usuário, 2026-07-22):** `RenderBuffer` (`BaseControlPrompt.cs:1523-1530`) tem um - "small-terminal safeguard": se `console.Height < MinSafeRenderHeight` (10) ou - `console.Width < MinSafeRenderWidth` (80) — `BaseControlPrompt.cs:67,73` —, chama - `ShowResizeWarningAndWait`, que bloqueia num loop até o terminal crescer, usando `console.CancelToken` - (não o `CancellationToken` passado a `Run()`!) como única saída (`BaseControlPrompt.cs:1626-1627`). - Como `VirtualTerminal.CancelToken => CancellationToken.None` (nunca cancela) e `Width`/`Height` do - `VirtualScreen` são fixos por design (grid determinístico, sem resize real), esse loop nunca termina — - nem o `CancellationToken` passado ao teste em `Run(token)` ajuda, porque o safeguard não olha para ele. - Diagnosticado instrumentando temporariamente `BaseControlPrompt.Run`/`SelectControl.TryResult` com - breadcrumbs em arquivo (revertido depois, nenhum vestígio ficou em produção). **Não é bug de - produção** (o safeguard faz sentido para um terminal real onde um humano redimensiona a janela ou - aperta Ctrl+C) — é uma regra obrigatória para qualquer teste de controle interativo: **sempre usar - `Width >= 80` e `Height >= 10`** em `VirtualTerminalOptions` (os defaults, seção A.1, já satisfazem - isso — o problema só aparece se um teste reduzir explicitamente as dimensões). Testar o aviso de - terminal pequeno em si (ROI baixo, fora do piloto) exigiria plumbing adicional no driver (ex.: linkar - `VirtualTerminal.CancelToken` a um `CancellationTokenSource` que o teste controla). - ---- - -## 14. Fase 2 (piloto) — cobertura por modo em Select/Input, achados e bug corrigido (2026-07-23) - -Depois da migração da seção 4 (D1 revista), o usuário pediu uma avaliação técnica+P.O. de expandir a -cobertura de `Input`/`Select` além do caminho feliz: cada controle tem um `TryResult` com uma cadeia -longa de `if/else if` por tecla, e as classes de apoio (`EmacsConsoleBuffer`, `Paginator` etc.) já -estavam exaustivamente testadas isoladamente — o gap real estava no *roteamento* da tecla pelo -controle + no *render* resultante (VT), camada que os testes de classe de apoio não alcançam. - -**Levantamento do código real revelou mais modos do que a suposição inicial:** `Select` tem 2 -(`Select`, `Filter`); `Input` tem **3** (`Input`, `History`, `Sugestions` — este último com 2 -sub-comportamentos: autocomplete inline vs dropdown). Reorganizado em uma suíte por modo, por -sugestão do usuário: - -``` -tests/PromptPlus.Tests/Controls/ - SelectControlTests.cs # globais + modo Select - SelectControlFilterModeTests.cs # modo Filter - InputControlTests.cs # globais + modo Input (edição básica, tooltip) - InputControlHistoryModeTests.cs # modo History - InputControlSuggestionsModeTests.cs # modo Sugestions (inline + dropdown) - InputSecretControlTests.cs # Secret/senha (F2) — API pública distinta (IInputSecretControl) -``` - -**Descobertas reais de comportamento** (confirmadas por código + execução, não são bug de teste): -- `Paginator.NextPage`/`PreviousPage` usam módulo (`Paginator.cs:220,239`) — paginação é cíclica, não - "clampada": PageDown na última página volta pra primeira; PageUp na primeira pula pra última. -- `PageUp` usa `IndexOption.LastItemWhenHasPages` — pousa no **último** item da página anterior, não - no primeiro (assimetria deliberada com PageDown, imita "rolar pra cima"). -- `FileHistory.LoadHistory` reordena por `TimeOutTicks` decrescente (`FileHistory.cs:55-57`) — não - preserva ordem de inserção; ao popular histórico em teste, timeouts iguais/próximos podem inverter - a ordem esperada por pura corrida de ticks (usar timeouts explicitamente distintos). -- `InputControl` reverte `ModeView.History`/`Sugestions` de volta pra `Input` (restaurando o texto - anterior) sempre que `Run()` termina por cancelamento fora do modo `Input` — diferente do `Select`, - cujo modo `Filter` sobrevive ao cancelamento. A técnica "sem tecla, espera cancelar" (funciona no - Select) não observa esses modos no Input; só um Enter/Tab real revela o estado. -- Abrir o dropdown de sugestões **não** carrega a sugestão destacada no buffer de edição — só - Tab/Enter (aceitar) fazem isso (`InputControl.cs:459,717`). Editar enquanto só navega o dropdown - continua a partir do texto de ANTES de abrir, não da sugestão em destaque. -- Shift+Tab só cicla pra trás dentro do dropdown (`autocomplete:false`) — com autocomplete inline - (`true`, default), Shift+Tab cai no handler genérico e não faz nada. - -**Bug real #8 (achado e corrigido)**: `InputControl.TryResult` (`InputControl.cs:415-428`) não setava -`ResultCtrl` no ramo de cancelamento, diferente do `SelectControl` (que seta). Isso fazia `TryResult` -retornar `false` mesmo cancelado; em `BaseControlPrompt.Run` (`BaseControlPrompt.cs:568`), a condição -do `do...while` avalia `TryResult` como parte de si mesma, então um retorno `false` faz o loop rodar -**uma passada extra de render** — essa passada reconstrói o template com `_errorMessage` já limpo (o -`WriteError` já tinha escrito+limpado na passada anterior), apagando da tela qualquer erro de -validação (`PredicateSelected`/`PredicateSelectedAsync` rejeitando) que estivesse visível bem antes do -controle sair de verdade. Confirmado com uma sonda que tira snapshot em pleno voo (thread de fundo, -antes de cancelar) vs. depois do cancelamento: o meio-de-voo mostrava a mensagem certinha, o final -não. Escopo real: só afeta cancelamento externo pegando o controle com um erro na tela — teclas -reais subsequentes (Escape, corrigir o texto) já seguiam o fluxo normal e funcionavam. **Corrigido** -adicionando `ResultCtrl = new ResultPrompt(_inputdata!.ToString(), true);` no ramo de -cancelamento, espelhando o `SelectControl`. Teste de regressão dedicado: -`InputControlTests.Cancellation_while_a_validation_error_is_showing_does_not_erase_it`. - -**Achado colateral (infraestrutura de teste, não é bug de produção)**: `InputControlHistoryModeTests` -e `Unit/FileHistoryTests.cs` trocam o mesmo campo estático `FileHistory.FileSystem` por um -`MockFileSystem`; como o xUnit roda classes de teste diferentes em paralelo por padrão, isso causava -corrida intermitente entre as duas classes. Corrigido com `[CollectionDefinition(..., -DisableParallelization = true)]` + `[Collection(...)]` nas duas classes (`FileHistoryCollection`, -definida em `InputControlHistoryModeTests.cs`), forçando-as a rodar sequencialmente uma em relação à -outra — outras classes continuam paralelas normalmente. - -**Cobertura adicional pedida explicitamente pelo usuário nesta rodada**: tecla de toggle de tooltip -(F1, cicla o índice) e show/hide (Ctrl+F1) em `Select` e `Input`; suíte separada para o controle -Secret/senha (`InputSecretControlTests.cs` — mascaramento com caractere default `#`/customizado, -F2 pra revelar/ocultar, `MaskSecret(enabledView:false)` desativando o F2, confirmação com o valor real -não-mascarado). - -**Correção 2026-07-23 (mesma sessão) — o gap do Ctrl+A/Ctrl+K era coberto, era só uma leitura errada -do código**: a primeira versão desta seção afirmava que `ConsoleHandler.EnabledEmacs` -(`InputControl.cs:357`) lia um singleton estático global (`ConsolePlusLibrary.ConsolePlus.EnabledEmacs`), -e por isso pulava testar Ctrl+A/Ctrl+K através do controle (risco de corrida entre classes de teste, -mesma categoria do `FileHistory.FileSystem`). Isso estava **errado**: `ConsoleHandler` dentro de -`InputControl` (e de todo `BaseControlPrompt`) é a propriedade de **instância** -`BaseControlPrompt.ConsoleHandler => console` (`BaseControlPrompt.cs:149`), não uma classe estática — -então `ConsoleHandler.EnabledEmacs` resolve pra `console.EnabledEmacs`, a propriedade da PRÓPRIA -`VirtualTerminal` injetada (`VirtualTerminal.cs:90`, autoproperty comum, default `false`). Nenhum -estado global envolvido, nenhum risco de corrida. Corrigido: `InputControlTests.cs` agora tem 3 -testes (`CtrlA_then_CtrlK_through_the_control_jumps_home_and_kills_to_the_end`, -`CtrlE_through_the_control_jumps_to_the_end`, -`CtrlA_then_CtrlK_is_a_no_op_when_emacs_bindings_are_disabled_on_the_console`), bastando -`vt.EnabledEmacs = true;` na instância antes de `Run()`. - -**Lacunas conhecidas, deixadas de fora deliberadamente (não é esquecimento)**: -- `AnsiDetector.cs`/`UnicodeDetector.cs` — **decisão definitiva do usuário (2026-07-23): não serão - cobertos.** Não é mais uma pendência aberta, é escopo fechado. -- Fase 2 **real** (rollout do mesmo padrão pros demais controles — 18 controles, 4 deles "Live") — - **planejamento controle a controle criado em 2026-07-23**, ver `tests/FASE2-CONTROLS-PLAN.md` - (só existe no PromptPlus, já que todos os controles são dele; é o tracker de progresso vivo desta - frente, atualizar lá — não aqui). Progresso até agora: **Grupo 1 concluído** (KeyPress, Switch, - Slider, ChartBar — 2 bugs reais de produção corrigidos no ChartBar + 1 gap do driver de teste - corrigido, SGR de 256 cores nunca implementado); Grupo 5 com infraestrutura pronta (`IFileSystem` - migrado em `FileControl`/`MultiFileControl`); Grupos 2-4 e 6 não iniciados. - -**Total ao fim desta frente**: 313 (ConsolePlus.Tests) + 250 (PromptPlus.Tests) = **563 testes**, -verdes de forma estável em múltiplas execuções completas seguidas (3 TFMs cada). 10 bugs reais de -produção corrigidos no total desde o início do projeto de testes. - ---- - -## Apêndice A — Código do driver (esqueletos completos) - -> **Convenções.** Namespace `ConsolePlusLibrary.Testing`. Código em inglês (segue o repo). Os esqueletos -> compilam contra a API real verificada (`IConsole`, `IConsolePlus`, `ConsoleWriter`, `Style`, `Color`, -> `ProfileConsole`, `IAnsiCommands`, `ColorSystem`, `AutoDetect`). Pontos que dependem de confirmação de -> assinatura estão marcados com `// VERIFY`. Fatos de emissão ANSI confirmados no código de produção: -> CSI = `ESC[`; DEC-private = `ESC[?`; CUP = `ESC[{row+1};{col+1}H` (1-indexed); EL = `ESC[{mode}K`; -> ED = `ESC[{mode}J`; SGR termina em `m`; cursor `ESC[?25h`/`ESC[?25l`; alt-screen `ESC[?1049h`/`l`; -> SGR truecolor = `38;2;r;g;b` (fg) / `48;2;r;g;b` (bg); reset = `0`. - -### A.1 `VirtualTerminalOptions` - -```csharp -namespace ConsolePlusLibrary.Testing; - -public sealed class VirtualTerminalOptions -{ - public int Width { get; set; } = 80; - public int Height { get; set; } = 24; - public ColorSystem ColorDepth { get; set; } = ColorSystem.TrueColor; // determinístico p/ estilo - public bool SupportsUnicode { get; set; } = true; - public bool Interactive { get; set; } = true; - public Color DefaultForeground { get; set; } = new(192, 192, 192); // VERIFY: cor default desejada - public Color DefaultBackground { get; set; } = new(0, 0, 0); -} -``` - -### A.2 `Cell` + `VirtualScreen` - -```csharp -using System.Text; - -namespace ConsolePlusLibrary.Testing; - -public readonly record struct Cell(Rune Glyph, Style Style) -{ - public static Cell Blank(Style style) => new(new Rune(' '), style); -} - -public sealed class VirtualScreen -{ - private readonly Cell[,] _cells; // [row, col] - public int Width { get; } - public int Height { get; } - public int CursorLeft { get; private set; } - public int CursorTop { get; private set; } - public Style Current { get; set; } - - public VirtualScreen(int width, int height, Style initial) - { - Width = width; Height = height; Current = initial; - _cells = new Cell[height, width]; - Fill(initial); - } - - public void Fill(Style style) - { - for (int r = 0; r < Height; r++) - for (int c = 0; c < Width; c++) - _cells[r, c] = Cell.Blank(style); - } - - public void SetCursor(int left, int top) - { - CursorLeft = Math.Clamp(left, 0, Width - 1); - CursorTop = Math.Clamp(top, 0, Height - 1); - } - - /// Stamps one rune at the cursor with and advances (wrap + scroll). - public void Put(Rune g) - { - if (CursorTop >= Height) ScrollUp(1); - _cells[CursorTop, CursorLeft] = new Cell(g, Current); - // NOTE: largura de glifo tratada como 1 aqui; para wide-char (CJK/emoji) avançar 2 e marcar - // a 2ª célula como continuação. Cobrir na Fase 1 se o piloto exercitar wide-chars. // VERIFY - if (++CursorLeft >= Width) { CursorLeft = 0; if (++CursorTop >= Height) ScrollUp(1); } - } - - public void NewLine() { CursorLeft = 0; if (++CursorTop >= Height) { CursorTop = Height - 1; ScrollUp(1); } } - public void CarriageReturn() => CursorLeft = 0; - - public void EraseInLine(int mode) // 0=cursor->eol, 1=bol->cursor, 2=linha toda - { - int from = mode == 0 ? CursorLeft : 0; - int to = mode == 1 ? CursorLeft : Width - 1; - for (int c = from; c <= to && c < Width; c++) _cells[CursorTop, c] = Cell.Blank(Current); - } - - public void EraseInDisplay(int mode) // 0=cursor->fim, 1=início->cursor, 2/3=tudo - { - if (mode >= 2) { Fill(Current); return; } - // 0 e 1: limpa a linha do cursor + linhas abaixo/acima - EraseInLine(mode); - if (mode == 0) for (int r = CursorTop + 1; r < Height; r++) BlankRow(r); - else for (int r = 0; r < CursorTop; r++) BlankRow(r); - } - - private void BlankRow(int r) { for (int c = 0; c < Width; c++) _cells[r, c] = Cell.Blank(Current); } - - private void ScrollUp(int n) - { - for (int i = 0; i < n; i++) - { - for (int r = 1; r < Height; r++) - for (int c = 0; c < Width; c++) _cells[r - 1, c] = _cells[r, c]; - BlankRow(Height - 1); - } - CursorTop = Math.Min(CursorTop, Height - 1); - } - - // ---- leitura para asserções ---- - public Style StyleAt(int row, int col) => _cells[row, col].Style; - public Rune GlyphAt(int row, int col) => _cells[row, col].Glyph; - - public string TextAt(int row, int col, int len) - { - var sb = new StringBuilder(); - for (int c = col; c < col + len && c < Width; c++) sb.Append(_cells[row, c].Glyph.ToString()); - return sb.ToString(); - } - - public string Snapshot() // grid inteiro como texto (trim trailing spaces por linha) - { - var sb = new StringBuilder(); - for (int r = 0; r < Height; r++) - { - var line = new StringBuilder(); - for (int c = 0; c < Width; c++) line.Append(_cells[r, c].Glyph.ToString()); - sb.AppendLine(line.ToString().TrimEnd()); - } - return sb.ToString(); - } -} -``` - -### A.3 `AnsiScreenInterpreter` (o `TextWriter` que vira células) - -```csharp -using System.Globalization; -using System.Text; - -namespace ConsolePlusLibrary.Testing; - -/// Interpreta o subconjunto de ANSI emitido pelo ConsolePlus e escreve no grid. -internal sealed class AnsiScreenInterpreter(VirtualScreen screen, VirtualTerminal owner) : TextWriter -{ - private enum State { Normal, Esc, Csi } - private State _state = State.Normal; - private bool _private; // sequência DEC-private (ESC[?) - private readonly StringBuilder _params = new(); - private char _pendingHighSurrogate = '\0'; - - public override Encoding Encoding => Encoding.UTF8; - - public override void Write(char value) => Feed(value); - public override void Write(string? value) { if (value != null) foreach (char ch in value) Feed(ch); } - public override void Write(char[] buffer, int index, int count) - { for (int i = 0; i < count; i++) Feed(buffer[index + i]); } - public override void WriteLine() => Feed('\n'); - - private void Feed(char ch) - { - switch (_state) - { - case State.Normal: - if (ch == '\x1b') { _state = State.Esc; return; } - if (ch == '\n') { screen.NewLine(); return; } - if (ch == '\r') { screen.CarriageReturn(); return; } - PutChar(ch); - return; - - case State.Esc: - if (ch == '[') { _state = State.Csi; _private = false; _params.Clear(); return; } - throw new NotSupportedException($"ESC {ch} não suportado pelo interpretador de teste."); - - case State.Csi: - if (ch == '?' && _params.Length == 0) { _private = true; return; } - if ((ch >= '0' && ch <= '9') || ch == ';') { _params.Append(ch); return; } - Dispatch(ch, _params.ToString()); - _state = State.Normal; - return; - } - } - - private void PutChar(char ch) - { - if (char.IsHighSurrogate(ch)) { _pendingHighSurrogate = ch; return; } - if (_pendingHighSurrogate != '\0') - { - screen.Put(new Rune(_pendingHighSurrogate, ch)); - _pendingHighSurrogate = '\0'; - return; - } - screen.Put(new Rune(ch)); - } - - private void Dispatch(char final, string prm) - { - switch (final) - { - case 'H': // CUP row;col (1-indexed) - { - var (a, b) = TwoParams(prm, 1, 1); - screen.SetCursor(left: b - 1, top: a - 1); - break; - } - case 'K': screen.EraseInLine(FirstParam(prm, 0)); break; // EL - case 'J': screen.EraseInDisplay(FirstParam(prm, 0)); break; // ED - case 'm': ApplySgr(prm); break; // SGR - case 'h': case 'l': - if (_private) HandlePrivateMode(prm, set: final == 'h'); - else throw new NotSupportedException($"CSI {prm}{final} não suportado."); - break; - default: - throw new NotSupportedException($"CSI {prm}{final} não suportado (surface no piloto)."); - } - } - - private void HandlePrivateMode(string prm, bool set) - { - switch (prm) - { - case "25": owner.SetCursorVisibleInternal(set); break; // show/hide cursor - case "1049": owner.SetAltScreenInternal(set); break; // alt screen - default: /* ignora outros modos privados irrelevantes p/ posição/estilo */ break; - } - } - - private void ApplySgr(string prm) - { - // Suporte primário: reset + truecolor (fg 38;2;r;g;b, bg 48;2;r;g;b). - // TODO Fase 2: 4-bit (30-37/90-97, 40-47/100-107) e 256 (38;5;n / 48;5;n) para testes dessas profundidades. - string[] p = prm.Length == 0 ? ["0"] : prm.Split(';'); - for (int i = 0; i < p.Length; i++) - { - int code = int.Parse(p[i], CultureInfo.InvariantCulture); - if (code == 0) { owner.ResetColorInternal(); } - else if ((code == 38 || code == 48) && i + 4 < p.Length && p[i + 1] == "2") - { - var color = new Color( - (byte)int.Parse(p[i + 2], CultureInfo.InvariantCulture), - (byte)int.Parse(p[i + 3], CultureInfo.InvariantCulture), - (byte)int.Parse(p[i + 4], CultureInfo.InvariantCulture)); - if (code == 38) owner.SetForegroundInternal(color); else owner.SetBackgroundInternal(color); - i += 4; - } - // else: código SGR não modelado (bold/underline/…) — ignorado até D4 decidir modelar atributos. - } - screen.Current = new Style(owner.ForegroundRgbColor, owner.BackgroundRgbColor); - } - - private static int FirstParam(string prm, int def) - => prm.Length == 0 ? def : int.Parse(prm.Split(';')[0], CultureInfo.InvariantCulture); - - private static (int, int) TwoParams(string prm, int d1, int d2) - { - if (prm.Length == 0) return (d1, d2); - string[] p = prm.Split(';'); - int a = p.Length > 0 && p[0].Length > 0 ? int.Parse(p[0], CultureInfo.InvariantCulture) : d1; - int b = p.Length > 1 && p[1].Length > 0 ? int.Parse(p[1], CultureInfo.InvariantCulture) : d2; - return (a, b); - } -} -``` - -### A.4 `InputQueue` - -```csharp -using System.Collections.Generic; - -namespace ConsolePlusLibrary.Testing; - -public sealed class InputQueue -{ - private readonly Queue _keys = new(); - - public bool HasNext => _keys.Count > 0; - - public InputQueue Enqueue(ConsoleKeyInfo key) { _keys.Enqueue(key); return this; } - - public InputQueue Enqueue(ConsoleKey key, bool shift = false, bool alt = false, bool ctrl = false) - => Enqueue(new ConsoleKeyInfo('\0', key, shift, alt, ctrl)); - - public InputQueue Type(string text) - { - foreach (char ch in text) - _keys.Enqueue(new ConsoleKeyInfo(ch, CharToKey(ch), shift: char.IsUpper(ch), alt: false, control: false)); - return this; - } - - public ConsoleKeyInfo Next() => _keys.Dequeue(); - - private static ConsoleKey CharToKey(char ch) - => char.IsLetter(ch) ? Enum.Parse(char.ToUpperInvariant(ch).ToString()) - : char.IsDigit(ch) ? (ConsoleKey)('0' + (ch - '0')) - : ConsoleKey.Oem1; // VERIFY: mapeamento de pontuação conforme necessidade dos testes -} -``` - -### A.5 `VirtualTerminal : IConsole, IConsolePlus` - -```csharp -using System.Text; -using ConsolePlusLibrary.ConsoleAbstractions; // ConsoleWriter (internal) — requer InternalsVisibleTo -using ConsolePlusLibrary.Core; // ProfileConsole (internal) - -namespace ConsolePlusLibrary.Testing; - -public sealed class VirtualTerminal : IConsole, IConsolePlus -{ - private readonly VirtualScreen _screen; - private readonly AnsiScreenInterpreter _interpreter; - private readonly ConsoleWriter _writer; // WRITER DE PRODUÇÃO - private readonly ProfileConsole _profile; // profile interno reutilizado - private readonly InputQueue _input; - private Color _fg, _bg; - private bool _cursorVisible = true; - private bool _writeToError; - private TargetScreen _buffer = TargetScreen.Primary; - - public InputQueue Keys => _input; - public VirtualScreen Screen => _screen; - - private VirtualTerminal(VirtualTerminalOptions o) - { - _fg = o.DefaultForeground; _bg = o.DefaultBackground; - _profile = new ProfileConsole - { - ProfileName = "VirtualTerminal", - IsTerminal = true, - Interactive = o.Interactive, - SupportsAnsi = AutoDetect.Yes, - SupportUnicode = o.SupportsUnicode ? AutoDetect.Yes : AutoDetect.No, - ColorDepth = o.ColorDepth, - DefaultForegroundColor = o.DefaultForeground, - DefaultBackgroundColor = o.DefaultBackground, - DetectedAnsiSupport = true, - DetectedUnicodeSupport = o.SupportsUnicode, - }; - _screen = new VirtualScreen(o.Width, o.Height, new Style(_fg, _bg)); - _interpreter = new AnsiScreenInterpreter(_screen, this); - _input = new InputQueue(); - _writer = new ConsoleWriter(this); // lê this.Profile.ColorDepth no ctor -> profile já pronto - } - - public static VirtualTerminal Create(Action? configure = null) - { - var o = new VirtualTerminalOptions(); - configure?.Invoke(o); - return new VirtualTerminal(o); - } - - // ---- callbacks internos do interpretador ---- - internal void SetForegroundInternal(Color c) => _fg = c; - internal void SetBackgroundInternal(Color c) => _bg = c; - internal void ResetColorInternal() { _fg = _profile.DefaultForegroundColor; _bg = _profile.DefaultBackgroundColor; } - internal void SetCursorVisibleInternal(bool v) => _cursorVisible = v; - internal void SetAltScreenInternal(bool alt) => _buffer = alt ? TargetScreen.Secondary : TargetScreen.Primary; - - // ---- IConsolePlus ---- - public bool WriteToErrorOutput { get => _writeToError; set => _writeToError = value; } - public ConsoleWriter Writer => _writer; - - // ---- capacidades / perfil ---- - public IProfileReadOnly Profile => _profile; - public IAnsiCommands Ansi => _writer.Ansi; - public bool SupportsAnsi => true; // força caminho ANSI determinístico - public bool SupportsUnicode => _profile.SupportUnicode == AutoDetect.Yes; - public ColorSystem ColorDepth => _profile.ColorDepth; - public int Width => _screen.Width; // fixo (não lê System.Console) - public int Height => _screen.Height; - public CancellationToken CancelToken => CancellationToken.None; - public bool EnabledEmacs { get; set; } - public event EventHandler? SizeChanged; - - /// Simula um resize para exercitar o relayout (Fase 3). - public void RaiseResize(int newWidth, int newHeight) - { - // Estratégia p/ largura/altura variáveis: recriar/rebindar o grid é uma opção; - // no mínimo, disparar o evento que BaseControlPrompt escuta. - SizeChanged?.Invoke(this, new ConsoleSizeChangedEventArgs - { - Width = newWidth, Height = newHeight, - PreviousWidth = _screen.Width, PreviousHeight = _screen.Height - }); - // NOTE: p/ variar dimensões de verdade, tornar Width/Height mutáveis e realocar o grid. // VERIFY (Fase 3) - } - - // ---- estilo / cor corrente ---- - public Style CurrentStyle => new(_fg, _bg); - public ConsoleColor ForegroundColor { get => Color.ToConsoleColor(_fg); set => _fg = value; } // VERIFY ToConsoleColor - public ConsoleColor BackgroundColor { get => Color.ToConsoleColor(_bg); set => _bg = value; } - public Color ForegroundRgbColor { get => _fg; set { _fg = value; _writer.ApplyStyle(new Style(_fg, _bg)); } } - public Color BackgroundRgbColor { get => _bg; set { _bg = value; _writer.ApplyStyle(new Style(_fg, _bg)); } } - public void ResetColor() { ResetColorInternal(); } - - // ---- I/O streams: AQUI o ANSI vira células ---- - public TextWriter Out => _interpreter; - public TextWriter Error => _interpreter; // erros no mesmo grid (ou um 2º grid, se necessário) - public TextReader In => TextReader.Null; - public void SetOut(TextWriter value) { /* no-op: saída é sempre o grid */ } - public void SetError(TextWriter value) { } - public void SetIn(TextReader value) { } - public bool IsInputRedirected => !_profile.Interactive; - public bool IsOutputRedirected => false; - public bool IsErrorRedirected => false; - public Encoding InputEncoding { get; set; } = Encoding.UTF8; - public Encoding OutputEncoding { get; set; } = Encoding.UTF8; - - // ---- núcleo de escrita (delegado ao writer REAL) ---- - public void Write(string? value, Style style, bool clearrestofline = false) - { if (value != null) _writer.WriteMarkupOutput(value, style, clearrestofline); } - - public void WriteRaw(string? value, Style style, bool clearrestofline = false) - { if (value != null) _writer.WriteOutput(value, style, clearrestofline); } - - public void WriteLine(string? value, Style style, bool clearrestofline = false) - { - if (value == null) return; - Write(value, style, clearrestofline); - _writer.WriteOutput([new Fragment("", null, FragmentKind.LineBreak)]); // VERIFY: Fragment é internal - } - - // ---- cursor: SEMPRE via grid (read-back fiel) ---- - public void SetCursorPosition(int left, int top) => _writer.Ansi.CursorPosition(top, left); // emite CUP -> grid - public int CursorLeft { get => _screen.CursorLeft; set => _writer.Ansi.CursorPosition(_screen.CursorTop, value); } - public int CursorTop { get => _screen.CursorTop; set => _writer.Ansi.CursorPosition(value, _screen.CursorLeft); } - public (int Left, int Top) GetCursorPosition() => (_screen.CursorLeft, _screen.CursorTop); - public bool CursorVisible { get => _cursorVisible; set => _cursorVisible = value; } - public bool HideCursor() { _cursorVisible = false; return true; } - public bool ShowCursor() { _cursorVisible = true; return true; } - - public void Clear(Color? backgroundcolor = null) - { - if (backgroundcolor.HasValue) _bg = backgroundcolor.Value; - _writer.Ansi.EraseInDisplay(2); - SetCursorPosition(0, 0); - } - - // ---- input (fila própria; NÃO usa System.Console) ---- - public bool KeyAvailable => _profile.Interactive && _input.HasNext; - public ConsoleKeyInfo ReadKey(bool intercept = false) => _input.Next(); - public Task ReadKeyAsync(bool intercept, CancellationToken ct) - => Task.FromResult(_input.HasNext ? _input.Next() : null); - public string? ReadLine() => null; - public int Read() => -1; - public void Beep() { } - - // ---- buffers ---- - public TargetScreen CurrentBuffer => _buffer; - public bool SwapBuffer(TargetScreen value) - { if (value == TargetScreen.Secondary) _writer.Ansi.EnterAltScreen(); else _writer.Ansi.ExitAltScreen(); return true; } - - // ================= Overloads tipados (mecânicos) ================= - // Os overloads Write/WriteLine/WriteFormat/WriteLineFormat para char, char[], object, bool, - // double, float, decimal, int, long e string (sem Style) NÃO têm lógica própria: apenas roteiam - // para o núcleo Write(string?,Style,bool) / WriteLine(string?,Style,bool) usando CurrentStyle, - // exatamente como em AnsiConsoleAdapter.cs. COPIAR VERBATIM daquele arquivo (blocos ~357–995 e - // ~1490–1557) — não requerem substituição, pois só dependem do núcleo já implementado acima. -} -``` - -### A.6 `ScreenAssertions` (helpers de asserção) - -```csharp -namespace ConsolePlusLibrary.Testing; - -public static class ScreenAssertions -{ - public static string TextAt(this VirtualTerminal vt, int row, int col, int len) - => vt.Screen.TextAt(row, col, len); - public static Style StyleAt(this VirtualTerminal vt, int row, int col) - => vt.Screen.StyleAt(row, col); - public static string Snapshot(this VirtualTerminal vt) => vt.Screen.Snapshot(); - - /// Primeira ocorrência de no grid, como (row, col) ou null. - public static (int Row, int Col)? Find(this VirtualTerminal vt, string text) - { - for (int r = 0; r < vt.Height; r++) - { - string line = vt.Screen.TextAt(r, 0, vt.Width); - int idx = line.IndexOf(text, StringComparison.Ordinal); - if (idx >= 0) return (r, idx); - } - return null; - } -} -``` - -### A.7 Projetos de teste (`.csproj`) - -`tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj`: - -```xml - - - net8.0;net9.0;net10.0 - false - enable - latest - - - - - - - - - - - - - - - - -``` - -`tests/PromptPlus.Tests/PromptPlus.Tests.csproj`: idêntico, trocando o `ProjectReference` para -`..\..\PromptPlus\src\PromptPlus.csproj` (que já traz o ConsolePlus transitivamente). - -### A.8 `InternalsVisibleTo` (no `src` de produção) - -`ConsolePlus/src/Properties/InternalsVisibleTo.cs` (ou no `.csproj`): - -```csharp -using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("ConsolePlus.Tests")] -[assembly: InternalsVisibleTo("PromptPlus.Tests")] // embeda o driver que toca internos do ConsolePlus -``` - -`PromptPlus/src/...`: - -```csharp -[assembly: InternalsVisibleTo("PromptPlus.Tests")] -``` - -### A.9 Testes de exemplo - -ConsolePlus — unidade + render de estilo (camada 1/2): - -```csharp -public class StyleRenderingTests -{ - [Fact] - public void Escreve_texto_com_cor_no_grid() - { - var vt = VirtualTerminal.Create(o => { o.ColorDepth = ColorSystem.TrueColor; }); - vt.WriteRaw("Ola", new Style(new Color(255, 0, 0), new Color(0, 0, 0))); - - vt.TextAt(0, 0, 3).Should().Be("Ola"); - vt.StyleAt(0, 0).Foreground.Should().Be(new Color(255, 0, 0)); - vt.CursorLeft.Should().Be(3); - } -} -``` - -PromptPlus — controle via driver injetado (camada 2): - -```csharp -public class SelectControlTests -{ - [Fact] - public void Select_confirma_item_navegado() - { - var vt = VirtualTerminal.Create(o => { o.Width = 80; o.Height = 24; }); - var cfg = new PromptConfig(); // internal -> InternalsVisibleTo - var controls = new PromptPlusControls(vt, cfg); // internal ctor - - vt.Keys.Enqueue(ConsoleKey.DownArrow).Enqueue(ConsoleKey.Enter); - - ISelectControl control = controls.Select("Escolha").AddItems(["A", "B", "C"]); - ResultPrompt result = control.Run(); - - result.IsAborted.Should().BeFalse(); - result.Content.Should().Be("B"); - vt.Find("B").Should().NotBeNull(); - } -} -``` - -> **D5 fechado:** `Select()`/`Input()` retornam interfaces públicas (`ISelectControl`, `IInputControl`); -> `Run(CancellationToken token = default)` retorna `ResultPrompt` com `Content`/`IsAborted`. Sem `dynamic`, -> sem cast para tipo concreto interno. - -### A.10 CI (GitHub Actions) - -`.github/workflows/tests.yml`: - -```yaml -name: tests -on: [push, pull_request] -jobs: - test: - strategy: - fail-fast: false - matrix: - os: [windows-latest, ubuntu-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 8.0.x - 9.0.x - 10.0.x - - run: dotnet test tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj -c Release - - run: dotnet test tests/PromptPlus.Tests/PromptPlus.Tests.csproj -c Release -``` - diff --git a/tests/_driver-src/AnsiScreenInterpreter.cs b/tests/_driver-src/AnsiScreenInterpreter.cs index c0728d2..eb07e98 100644 --- a/tests/_driver-src/AnsiScreenInterpreter.cs +++ b/tests/_driver-src/AnsiScreenInterpreter.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** using System; @@ -153,11 +153,11 @@ private void ApplySgr(string prm) // 8-bit indexed SGR (38;5;n / 48;5;n) — AnsiColorBuilder.GetEightBit emits this // whenever a Color has .Number set (named ConsoleColor constants, and anything // produced by Style.FindStyle's contrast adjustment, which snaps the foreground to - // the "nearest palette color" — confirmed hit by SwitchStyles.Slider's default - // Style(ConsoleColor.White, ConsoleColor.DarkGray) during the Fase 2 Grupo 1 pass, - // 2026-07-23). Resolve the index against ConsolePlus's own already-tested 256-color - // table (ColorPalette.EightBit) instead of re-deriving the xterm cube/grayscale - // formula here, so the driver can never drift from what production actually uses. + // the "nearest palette color" — confirmed hit by SliderStyles.Slider's default + // Style(ConsoleColor.White, ConsoleColor.DarkGray)). Resolve the index against + // ConsolePlus's own already-tested 256-color table (ColorPalette.EightBit) instead + // of re-deriving the xterm cube/grayscale formula here, so the driver can never + // drift from what production actually uses. int index = int.Parse(p[i + 2], CultureInfo.InvariantCulture); Color palette = ColorPalette.EightBit[index]; var color = new Color(palette.R, palette.G, palette.B); diff --git a/tests/_driver-src/Cell.cs b/tests/_driver-src/Cell.cs index 151ade3..b5cba37 100644 --- a/tests/_driver-src/Cell.cs +++ b/tests/_driver-src/Cell.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** using System.Text; diff --git a/tests/_driver-src/InputQueue.cs b/tests/_driver-src/InputQueue.cs index 3b1aa32..93975c8 100644 --- a/tests/_driver-src/InputQueue.cs +++ b/tests/_driver-src/InputQueue.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** using System; @@ -71,7 +71,7 @@ private static ConsoleKey CharToKey(char ch) '-' => ConsoleKey.OemMinus, '.' => ConsoleKey.OemPeriod, ',' => ConsoleKey.OemComma, - _ => ConsoleKey.Oem1, // extend as pilot tests need specific punctuation + _ => ConsoleKey.Oem1, // extend as tests need specific punctuation }; } } diff --git a/tests/_driver-src/ScreenAssertions.cs b/tests/_driver-src/ScreenAssertions.cs index 4271de8..0b916f5 100644 --- a/tests/_driver-src/ScreenAssertions.cs +++ b/tests/_driver-src/ScreenAssertions.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** using System; diff --git a/tests/_driver-src/VirtualScreen.cs b/tests/_driver-src/VirtualScreen.cs index cf564b4..22b789c 100644 --- a/tests/_driver-src/VirtualScreen.cs +++ b/tests/_driver-src/VirtualScreen.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** using System; @@ -94,8 +94,8 @@ public void Put(Rune g) ScrollUp(1); } _cells[CursorTop, CursorLeft] = new Cell(g, Current); - // Glyph width is treated as 1 here; wide glyphs (CJK/emoji) are out of scope for the - // Fase 1 pilot (Input+Select) — revisit if a later control's tests need it (TEST-PLAN.md D4). + // Glyph width is treated as 1 here; wide glyphs (CJK/emoji) are out of scope for + // now — revisit if a later control's tests need it. if (++CursorLeft >= Width) { CursorLeft = 0; diff --git a/tests/_driver-src/VirtualTerminal.Overloads.cs b/tests/_driver-src/VirtualTerminal.Overloads.cs index dc8f504..b37fe12 100644 --- a/tests/_driver-src/VirtualTerminal.Overloads.cs +++ b/tests/_driver-src/VirtualTerminal.Overloads.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** // Typed Write/WriteLine/WriteFormat/WriteLineFormat overloads for char, char[], object, bool, double, diff --git a/tests/_driver-src/VirtualTerminal.cs b/tests/_driver-src/VirtualTerminal.cs index ba5769c..d42c63e 100644 --- a/tests/_driver-src/VirtualTerminal.cs +++ b/tests/_driver-src/VirtualTerminal.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** using ConsolePlusLibrary.ConsoleAbstractions; @@ -16,7 +16,7 @@ namespace ConsolePlusLibrary.Testing /// /// In-memory / that hosts the real /// and interprets its ANSI output into a , giving deterministic, headless - /// read-back of cursor position and style. See tests/TEST-PLAN.md sections 3 and 5. + /// read-back of cursor position and style. /// public sealed partial class VirtualTerminal : IConsole, IConsolePlus { diff --git a/tests/_driver-src/VirtualTerminalOptions.cs b/tests/_driver-src/VirtualTerminalOptions.cs index ec41e66..c0ed1c4 100644 --- a/tests/_driver-src/VirtualTerminalOptions.cs +++ b/tests/_driver-src/VirtualTerminalOptions.cs @@ -1,6 +1,6 @@ // *************************************************************************************** // MIT LICENCE -// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source, see tests/TEST-PLAN.md) +// Headless test driver shared by ConsolePlus.Tests and PromptPlus.Tests (linked source) // *************************************************************************************** namespace ConsolePlusLibrary.Testing @@ -13,7 +13,7 @@ namespace ConsolePlusLibrary.Testing /// constructor (new Color(r, g, b)), never from a named palette constant (e.g. Color.White). /// Named constants carry an internal palette Number, which makes /// fall back to 8-bit SGR (38;5;n/48;5;n) instead of truecolor (38;2;r;g;b) — a sequence - /// does not model yet (Fase 2, see TEST-PLAN.md D4/A.3). + /// does not model yet (Fase 2). /// public sealed class VirtualTerminalOptions {