From e684f80ba3fcade542d239649969177e5cd94a94 Mon Sep 17 00:00:00 2001 From: Fernando Cerqueira Date: Wed, 29 Jul 2026 10:10:19 -0300 Subject: [PATCH 1/7] Fix ADR Markdown formatting defect and move adr-config.adrplus to repo root Six ADR files had a leading tab before the header block that made the disclaimer/table render as a code block under CommonMark. Also relocated adr-config.adrplus from docs/adr/ into the repo root (where the installed adrplus tool actually resolves it from) and filled in its previously empty template field with the real Context/Decision/Consequences skeleton. --- docs/adr/adr-config.adrplus => adr-config.adrplus | 2 +- docs/adr/ADR0001V01R02-SplitConsolePlusAndPromptPlus.md | 6 +++--- docs/adr/ADR0011V01R01-ShutdownStateRestoration.md | 6 +++--- docs/adr/ADR0012V01R01-EmojiShortcodeModel.md | 6 +++--- docs/adr/ADR0013V01R01-LowLevelAnsiAndAlternateScreen.md | 6 +++--- docs/adr/ADR0014V01R02-GeneratedApiDocsOffLimits.md | 6 +++--- docs/adr/ADR0015V01R01-RedirectedConsoleIoContract.md | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) rename docs/adr/adr-config.adrplus => adr-config.adrplus (77%) 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/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 -``` - From a14469b69e1951b2e3f29c138b0be1b970adefbd Mon Sep 17 00:00:00 2001 From: Fernando Cerqueira Date: Wed, 29 Jul 2026 10:10:45 -0300 Subject: [PATCH 3/7] Clean up code comments: translate to English, strip plan/phase references Swept // , /* */ and /// comments across src/ and tests/ (excluding license headers and auto-generated files). Translated remaining Portuguese comments to English, removed references to internal rollout plans/phases now long finished ("Fase 1/2", "Grupo N"), and dropped comments that only restated obvious code instead of explaining a non-obvious why. No code logic changed. --- src/ConsoleAbstractions/AnsiColorBuilder.cs | 4 +- src/ConsoleAbstractions/AnsiConsoleAdapter.cs | 15 +++---- .../NoAnsiConsoleAdapter.cs | 14 +++---- src/ConsoleAbstractions/ProfileExtensions.cs | 2 +- src/ConsolePlus.Extra.cs | 2 +- src/Core/AnsiDetector.cs | 5 --- src/Core/EnvironmentUtil.cs | 17 +------- src/Core/UnicodeDetector.cs | 15 ++++--- src/Properties/AssemblyInfo.cs | 1 - src/Shared/Color.cs | 6 --- src/Shared/EmacsConsoleBuffer.cs | 40 +++---------------- .../Rendering/BannerWidgetTests.cs | 6 +-- .../Rendering/OverflowTests.cs | 2 +- .../Unit/AnsiColorBuilderTests.cs | 2 +- .../Unit/AnsiCommandsTests.cs | 12 +++--- .../Unit/ColorExtensionsTests.cs | 2 +- .../Unit/ColorJsonConverterTests.cs | 2 +- .../Unit/ColorPaletteTests.cs | 3 +- .../Unit/ColorTableCssTests.cs | 4 +- tests/ConsolePlus.Tests/Unit/ColorTests.cs | 3 +- .../Unit/ConsoleKeyInfoExtensionsTests.cs | 10 ++--- .../Unit/CultureExtensionsTests.cs | 2 +- .../Unit/CultureInfoJsonConverterTests.cs | 2 +- tests/ConsolePlus.Tests/Unit/DashUtilTests.cs | 4 +- .../Unit/DisplayLengthTests.cs | 2 +- .../Unit/EmacsConsoleBufferTests.cs | 4 +- .../Unit/EmojiGroupResolutionTests.cs | 6 +-- tests/ConsolePlus.Tests/Unit/EmojiTests.cs | 2 +- .../ConsolePlus.Tests/Unit/FigletFontTests.cs | 5 +-- .../Unit/MarkupColorTokenizerTests.cs | 6 +-- .../Unit/MarkupHelperTests.cs | 4 +- tests/ConsolePlus.Tests/Unit/MarkupTests.cs | 2 +- .../Unit/ProfileExtensionsTests.cs | 31 +++++++------- .../Unit/StringExtensionsNewLineTests.cs | 6 +-- .../Unit/StyleExtensionsTests.cs | 4 +- .../Unit/TextWriterExtensionsTests.cs | 8 ++-- tests/_driver-src/AnsiScreenInterpreter.cs | 10 ++--- tests/_driver-src/Cell.cs | 2 +- tests/_driver-src/InputQueue.cs | 4 +- tests/_driver-src/ScreenAssertions.cs | 2 +- tests/_driver-src/VirtualScreen.cs | 6 +-- .../_driver-src/VirtualTerminal.Overloads.cs | 2 +- tests/_driver-src/VirtualTerminal.cs | 4 +- tests/_driver-src/VirtualTerminalOptions.cs | 4 +- 44 files changed, 111 insertions(+), 178 deletions(-) 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/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/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/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/_driver-src/AnsiScreenInterpreter.cs b/tests/_driver-src/AnsiScreenInterpreter.cs index c0728d2..edcf948 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; @@ -154,10 +154,10 @@ private void ApplySgr(string prm) // 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. + // 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 { From 694812596694a2d37ff6a98c00c3d719aeeb7065 Mon Sep 17 00:00:00 2001 From: Fernando Cerqueira Date: Wed, 29 Jul 2026 10:21:00 -0300 Subject: [PATCH 4/7] Fix corrupted license header and completed a truncated XML doc comment Bitbucket.cs had a duplicated/merged license-header fragment on line 1. Style.cs's sentence was cut off mid-clause; completed it to describe the implicit Color-to-Style conversion it was referring to. --- src/RuntimeEnvironment/Bitbucket.cs | 2 +- src/Shared/Style.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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/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. From 6f8c52ebaba0a531888736b618707ed8a895a4f5 Mon Sep 17 00:00:00 2001 From: Fernando Cerqueira Date: Wed, 29 Jul 2026 11:15:28 -0300 Subject: [PATCH 5/7] ConsolePlus Added NuGet and download badges to README for ConsolePlus. --- README.md | 1 + tests/_driver-src/AnsiScreenInterpreter.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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/tests/_driver-src/AnsiScreenInterpreter.cs b/tests/_driver-src/AnsiScreenInterpreter.cs index edcf948..eb07e98 100644 --- a/tests/_driver-src/AnsiScreenInterpreter.cs +++ b/tests/_driver-src/AnsiScreenInterpreter.cs @@ -153,7 +153,7 @@ 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 + // 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 From 39a2e74f5e8e7f3f96ac33122aba55b6cdff729d Mon Sep 17 00:00:00 2001 From: Fernando Cerqueira Date: Wed, 29 Jul 2026 11:17:19 -0300 Subject: [PATCH 6/7] Bump version to 1.0.0-Beta5 in ConsolePlus.csproj Updated the project version in ConsolePlus.csproj from 1.0.0-Beta4 to 1.0.0-Beta5. No other changes were made. --- src/ConsolePlus.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d5bed605bfd714059f8deb37b3c78244138e8451 Mon Sep 17 00:00:00 2001 From: Fernando Cerqueira Date: Wed, 29 Jul 2026 11:30:18 -0300 Subject: [PATCH 7/7] Clarify parameter and enhance Style doc remarks Updated ConsolePlus.WriteLines(int) docs to clarify the 'steps' parameter as "Number of lines." Improved Style struct and Background property documentation with remarks on implicit Color conversion for quick foreground-only styles. --- docs/api/ConsolePlus.md | 2 +- docs/api/Style.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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