Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/validator.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2024-05-24 - Exhaustive Spatial and Hierarchical Validation of Table Component
**Observation:** Auditing the `Table` component revealed requirements for robust testing against extreme constraints and explicit character verifications, especially when `BoxStyle.Heavy` is enabled. Traditional tests do not adequately cover the absolute positional accuracy of characters under dynamic grid-based hierarchical structures, nor do they isolate rendering behaviors when constrained to extremely tight boundaries (e.g., `Size(0,0)`). The control encapsulates complex subcomponents such as `ScrollViewer`, `TableRow`, and internal layout logic.
**Strategic Action:** Developed an exhaustive validation suite (`ValidatorTableTests.cs`) under the xUnit framework. The matrix enforces: 1) Hierarchical Composition and Dynamic Mutation validation by embedding the `Table` alongside a `Border` within a proportionally-sized `Grid` and verifying structural integrity upon parent resize. 2) Coordinate-Precise Assertion evaluating the exact mapped placement of Unicode Box Drawing (`Heavy` style) symbols (e.g., `\u250F`, `\u2523`) against a `VirtualBuffer`. 3) Boundary verification enforcing correct behavior at `0x0` and `1x1` dimensions.

## 2024-05-18 - Exhaustive Spatial Validation for Table Component (BoxStyle.Heavy)
**Observation:** The `Table` component was missing strict body separation validation for `BoxStyle.Heavy`, falling back to default or unoptimized rendering paths for inner cross junctions and horizontal separators. Legacy tests validated single and double styles but left `Heavy` unchecked, potentially allowing rendering regression.
**Strategic Action:** Analyzed `TableBoxChars` to correctly assign heavy drawing characters (`BodySepH: \u2501`, `BodySepTLeft: \u2523`, `BodySepTRight: \u252B`, `BodySepCross: \u254B`). Refactored `TableSeparator` to directly consume these characters from `TableBoxChars` instead of legacy `BoxDrawingChars.GetInterior`. Implemented `Table_CoordinatePreciseCharacterAssertion_HeavyBodySeparation` to mandate exact X/Y coordinate assertion of the characters in the VirtualBuffer, validating hierarchical layout boundary behaviors.
Comment on lines 1 to +7
8 changes: 4 additions & 4 deletions src/Tedd.TUI.Tests/TableCoverageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,18 +363,18 @@ public void Table_Separator_Rendering()
// 6: └───────┘

// Row 4 should be separator
// X=0: Left Junction (u2520 ┠) if ShowBorder && ShowHorizontalLines
// X=0: Left Junction (u2523 ┣) if ShowBorder && ShowHorizontalLines
// X=1..3: Horz Line (u2501 ━) — matches default BorderStyle.Heavy
// X=4: Cross (u254B ╋) if ShowVerticalLines
// X=5..7: Horz Line
// X=8: Right Junction (u2528 ┨)
// X=8: Right Junction (u252B ┫)
Comment on lines 365 to +370

// Verify Row 4
Assert.Equal('\u2520', buffer.GetPixel(0, 4).Character);
Assert.Equal('\u2523', buffer.GetPixel(0, 4).Character);
Assert.Equal('\u2501', buffer.GetPixel(1, 4).Character);
Assert.Equal('\u254B', buffer.GetPixel(4, 4).Character); // Cross
Assert.Equal('\u2501', buffer.GetPixel(5, 4).Character);
Assert.Equal('\u2528', buffer.GetPixel(8, 4).Character);
Assert.Equal('\u252B', buffer.GetPixel(8, 4).Character);
}

[Fact]
Expand Down
64 changes: 64 additions & 0 deletions src/Tedd.TUI.Tests/ValidatorTableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,70 @@ public void Table_CoordinatePreciseCharacterAssertion_SingleStyle()
Assert.Equal('\u2534', buffer.GetPixel(4, 7).Character);
}

[Fact]
public void Table_CoordinatePreciseCharacterAssertion_HeavyBodySeparation()
{
// Architectural Mandate: Nest in a hierarchy and trigger dynamic resize
var parentGrid = new Grid();
parentGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
parentGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });

var table = new Table
{
ShowBorder = true,
ShowHeader = true,
ShowVerticalLines = true,
ShowHorizontalLines = true,
BorderStyle = BoxStyle.Heavy
};

var col1 = new TableColumn { Header = "ID", Width = new GridLength(3, GridUnitType.Pixel) };
var col2 = new TableColumn { Header = "Name", Width = new GridLength(4, GridUnitType.Pixel) };
table.Columns.Add(col1);
table.Columns.Add(col2);

table.AddRow("1", "Bob");
table.AddRow("2", "Alice");

parentGrid.Children.Add(table);

// Initial layout pass
parentGrid.Measure(new Size(12, 8));
parentGrid.Arrange(new Rect(0, 0, 12, 8));

var sv = table.GetVisualChild(0) as ScrollViewer;
if (sv != null)
{
sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
}

Comment on lines +345 to +355
var buffer = new VirtualBuffer(12, 8);
parentGrid.Render(buffer, 0, 0);

// Assert coordinates on initial constraint
Assert.Equal('\u254B', buffer.GetPixel(4, 2).Character);
Assert.Equal('\u2523', buffer.GetPixel(0, 4).Character);
Assert.Equal('\u252B', buffer.GetPixel(11, 4).Character);
Assert.Equal('\u254B', buffer.GetPixel(4, 4).Character);
Assert.Equal('\u2501', buffer.GetPixel(2, 4).Character);

// Dynamic Resize state mutation
parentGrid.Measure(new Size(20, 10));
parentGrid.Arrange(new Rect(0, 0, 20, 10));

var bufferResize = new VirtualBuffer(20, 10);
parentGrid.Render(bufferResize, 0, 0);

// Table spans full star width, so width is 20 now.
// Body Sep Right is now at 19. Cross remains at 4 because column widths are fixed pixels.
Assert.Equal('\u254B', bufferResize.GetPixel(4, 2).Character); // Header cross junction
Assert.Equal('\u2523', bufferResize.GetPixel(0, 4).Character); // Left junction
Assert.Equal('\u252B', bufferResize.GetPixel(19, 4).Character); // Right junction moved to 19
Assert.Equal('\u254B', bufferResize.GetPixel(4, 4).Character); // Cross remains at 4
Assert.Equal('\u2501', bufferResize.GetPixel(2, 4).Character); // Horizontal remains line
}

[Fact]
public void Table_CoordinatePreciseCharacterAssertion_DoubleStyle()
{
Expand Down
15 changes: 9 additions & 6 deletions src/Tedd.TUI/Table.cs
Original file line number Diff line number Diff line change
Expand Up @@ -409,16 +409,18 @@ public static TableBoxChars Get(BoxStyle style)
c.TLeft = '\u2523';
c.TRight = '\u252B';
c.HeaderCross = '\u254B';
c.BodySepTLeft = '\u2520';
c.BodySepTRight = '\u2528';
c.BodySepCross = '\u2542'; // ╂ (Heavy Vert, Light Horz)
c.BodySepH = '\u2501';
c.BodySepTLeft = '\u2523';
c.BodySepTRight = '\u252B';
c.BodySepCross = '\u254B';
break;
case BoxStyle.Double:
c.TDown = '\u2566';
c.TUp = '\u2569'; // ╧ (Double Horz, Single Up)
c.TLeft = '\u2560';
c.TRight = '\u2563';
c.HeaderCross = '\u256C';
c.BodySepH = '\u2500'; // Light horizontal for Double body sep
c.BodySepTLeft = '\u255F'; // ╟ (Double Vert, Single Right)
c.BodySepTRight = '\u2562'; // ╢ (Double Vert, Single Left)
c.BodySepCross = '\u256B'; // ╫ (Double Vert, Single Horz)
Expand All @@ -429,6 +431,7 @@ public static TableBoxChars Get(BoxStyle style)
c.TLeft = '\u251C';
c.TRight = '\u2524';
c.HeaderCross = '\u253C';
c.BodySepH = '\u2500';
c.BodySepTLeft = '\u251C';
c.BodySepTRight = '\u2524';
c.BodySepCross = '\u253C';
Expand Down Expand Up @@ -1076,9 +1079,9 @@ public override void Render(VirtualBuffer buffer, int offsetX, int offsetY)
// rather than the infinite horizontal scroll space given by ScrollViewer.
int width = Math.Min(RenderSize.Width, table.RenderSize.Width);

var interior = BoxDrawingChars.GetInterior(table.BorderStyle);
char hChar = interior.Horizontal;
char crossChar = BoxDrawingChars.GetInteriorCross(table.BorderStyle);
var chars = Table.TableBoxChars.Get(table.BorderStyle);
char hChar = chars.BodySepH;
char crossChar = chars.BodySepCross;

buffer.DrawHLine(x, y, width, hChar, TuiColor.Gray, TuiColor.Black);

Expand Down
Loading