[patch] Fix NotImplementedException on the default render path - #104
Merged
Conversation
TextElement and BorderElement declared explicit switch arms that threw NotImplementedException for enum members whose behaviour the `_` fallback immediately below already implemented. An explicit arm is matched before `_`, so the throw won. In every case the throwing member was the property's default value, so the failure was reached by the most ordinary use of the library: - TextElement.HorizontalAlignment defaults to Left, which threw. - TextElement.VerticalAlignment defaults to Top, which threw. - BorderElement.TitleAlignment defaults to Left, so any titled border threw. - BorderStyle.None threw instead of drawing no border. CalculateHorizontalPosition and CalculateVerticalPosition are called unconditionally from TextElement.OnRender, so this was the main render path rather than a rare branch. Rendering a TextElement with nothing but its text set threw. The three alignment arms now return what the fallback returned. BorderStyle.None gets a real implementation rather than the fallback's single-line border: OnRender returns before drawing, so no border and no title are drawn, while the base class still renders children and the element stays a usable container. Tests: the suite was green throughout because nothing called Render. There was no TextElementTests at all, and BorderElementTests covered only property round-tripping and invalidation. Adds RecordingConsoleProvider, an IConsoleProvider double that records WriteAt calls so tests can assert what was drawn and where, plus TextElementTests and BorderElementRenderTests covering the full alignment and border-style matrix via [DynamicData] over Enum.GetValues. Verified by mutation: reintroducing the three throws fails 21 of the 113 tests. Fixes #102 Fixes #103 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DTHNXgSNEHUSQ5KMLgivno
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #102. Fixes #103.
The bug
TextElementandBorderElementdeclared explicitswitcharms that threwNotImplementedExceptionfor enum members whose behaviour the_ =>fallback immediately below already implemented. An explicit arm is matched before_, so the throw won.In every case the throwing member was the property's default value, so the failure was reached by the most ordinary use of the library — not by an exotic configuration:
TextElement.CalculateHorizontalPositionHorizontalAlignment.LeftHorizontalAlignment = LeftTextElement.CalculateVerticalPositionVerticalAlignment.TopVerticalAlignment = TopBorderElementtitle placementHorizontalAlignment.LeftTitleAlignment = LeftBorderElement.GetBorderCharactersBorderStyle.NoneBoth
Calculate*Positionmethods are called unconditionally fromTextElement.OnRender, so this was the main render path. Rendering aTextElementwith nothing but its text set threw.The fix
The three alignment arms now return what the fallback returned —
baseX,baseY, andposition.X + 1. These are pure behaviour restorations, not new logic.BorderStyle.Noneneeded a real decision rather than a deletion, because the_ =>fallback inGetBorderCharactersisSingleLine— falling through would have silently drawn the wrong thing.OnRendernow returns before drawing when the style isNone, so no border and no title are drawn, whileUIContainerBasestill renders children afterwards and the element remains a usable container.One deliberate non-change:
BorderElement's constructor setsPadding = new Padding(1, 1, 1, 1)unconditionally, so aNoneborder still insets its children by one cell. Making padding depend on the style is a design decision beyond this fix, and callers can already setPadding = Padding.None.Why it shipped, and what stops it recurring
The suite was green throughout. There was no
TextElementTestsat all, andBorderElementTestscovered only property round-tripping and invalidation — nothing calledRender.This PR adds:
RecordingConsoleProvider— anIConsoleProviderdouble that records everyWriteAtcall instead of drawing, so tests assert what was drawn and where. Rendering to the realSpectreConsoleProviderwrites to the runner's console and leaves output unobservable.TextElementTests— the full alignment matrix via[DynamicData]overEnum.GetValues, plus positional assertions (left at the content origin, right flush to the edge, centre between them), padding, word wrap, the height clamp, empty text, and invisibility.BorderElementRenderTests— everyBorderStyle×TitleAlignmentpairing, per-style corner glyphs, theNonebehaviour including that children still render, the 2×2 floor, and theWidth > 4title threshold.Driving the matrices from
Enum.GetValuesrather than hardcoded lists means a newly added enum member is covered automatically instead of silently skipped — which is the specific shape of the original miss.Verification
113 tests pass, up from 71.
Mutation-checked: reintroducing the three alignment throws by substitution fails 21 of 113 tests, so the new coverage is load-bearing rather than incidental. The tree was restored from backups and re-verified afterwards (
grepforNotImplementedExceptionacrossTUI.Corereturns nothing but build artifacts, and the diff is only the intended four changes).Also confirmed end-to-end against a standalone harness compiling the real
TUI.Coresources: all 28 alignment/style combinations render, where previously the default configuration threw.Note for reviewers
CLAUDE.mdgains a Render tests subsection covering the recorder, the enum-matrix convention, and the two sizing traps that bite when writing these (an element smaller than its own padding has an empty content area and draws nothing;BorderElementsuppresses its title belowWidth > 4). The second one cost me a red test while writing this.I could not run the repo's own analyzers locally — this sandbox's SDK is 10.0.111 (Roslyn
5.0.0.0) andktsu.Sdk.Analyzers2.28.0 requires5.9.0.0, so CSC refuses to load it withCS9057. The .NET analyzers andEnforceCodeStyleInBuilddid run and are clean (two IDE violations were caught and fixed that way). CI is the first place the ktsu analyzers will actually run against this change.Generated by Claude Code