From 4d33fcf24467585919624643866b42acd1c44173 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 02:30:40 +0000 Subject: [PATCH] [patch] Measure a container with no room past its padding as the padding alone UIContainerBase measured its children at width 0 when the container got no more width than its padding, and TextElement reads 0 as "no constraint". A BorderElement squeezed to 2 columns then reported its child's full unwrapped height, drew a hollow box that tall and pushed the siblings below it down. The container now returns the padding alone in that case, as TextElement does since #198. Fixes ktsu-dev/TUI#249 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW4towbdqS7BHdCDNqm --- TUI.Test/ContainerNoRoomMeasureTests.cs | 69 +++++++++++++++++++++++++ TUI/Elements/UIContainerBase.cs | 11 +++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 TUI.Test/ContainerNoRoomMeasureTests.cs diff --git a/TUI.Test/ContainerNoRoomMeasureTests.cs b/TUI.Test/ContainerNoRoomMeasureTests.cs new file mode 100644 index 0000000..a1be514 --- /dev/null +++ b/TUI.Test/ContainerNoRoomMeasureTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors + +namespace ktsu.TUI.Test; + +using ktsu.TUI.Core.Elements.Layouts; +using ktsu.TUI.Core.Elements.Primitives; +using ktsu.TUI.Core.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Tests for a container measured at a width its padding takes entirely (ktsu-dev/TUI#249). +/// +/// +/// The container measured its children at width 0, which reads as "no +/// constraint", so a word-wrapped child reported its full unwrapped height for text that could +/// never be drawn, and the rows it claimed were taken from the siblings below. +/// +[TestClass] +public sealed class ContainerNoRoomMeasureTests +{ + /// + /// A border measured at width 1 or 2 reports the bare border, whatever its child is. + /// + /// A width no wider than the border's padding. + [TestMethod] + [DataRow(1)] + [DataRow(2)] + public void BorderWithNoRoomForItsChildMeasuresAsTheBareBorder(int availableWidth) + { + BorderElement border = new() { Child = new TextElement("one\ntwo\nthree\nfour\nfive") { WordWrap = true } }; + + Assert.AreEqual(new Dimensions(2, 2), border.CalculateRequiredDimensions(availableWidth)); + } + + /// + /// A border given one more column than its padding still measures its child. + /// + [TestMethod] + public void BorderWithOneColumnOfRoomStillMeasuresItsChild() + { + BorderElement border = new() { Child = new TextElement("ab") { WordWrap = true } }; + + Assert.AreEqual(new Dimensions(3, 4), border.CalculateRequiredDimensions(3)); + } + + /// + /// The issue's layout: a border squeezed to its padding width beside a label leaves the row + /// below where the bare border ends, not where its unwrapped child would. + /// + [TestMethod] + public void SqueezedBorderDoesNotPushTheNextRowDown() + { + BorderElement border = new() { Child = new TextElement("one\ntwo\nthree\nfour\nfive") { WordWrap = true } }; + StackPanel row = new(Orientation.Horizontal); + row.AddChild(new TextElement("Label text here: ")); + row.AddChild(border); + StackPanel panel = new(Orientation.Vertical); + panel.AddChild(row); + panel.AddChild(new TextElement("below")); + panel.Position = Position.Origin; + panel.Dimensions = new Dimensions(19, 20); + RecordingConsoleProvider provider = new(); + + panel.ArrangeChildren(); + panel.Render(provider); + + Assert.AreEqual(2, provider.WritesOf("below").Single().Position.Y); + } +} diff --git a/TUI/Elements/UIContainerBase.cs b/TUI/Elements/UIContainerBase.cs index 15e5ba7..be4db3a 100644 --- a/TUI/Elements/UIContainerBase.cs +++ b/TUI/Elements/UIContainerBase.cs @@ -141,7 +141,16 @@ protected override Dimensions OnCalculateRequiredDimensions() /// protected override Dimensions OnCalculateRequiredDimensions(int availableWidth) { - Dimensions requiredDimensions = OnCalculateRequiredDimensionsForChildren(Math.Max(0, availableWidth - Padding.Horizontal)); + if (availableWidth > 0 && availableWidth <= Padding.Horizontal) + { + // The padding takes every column, so no child can be drawn. Measuring the children at + // width 0 would read as "no constraint", and a word-wrapped child would then claim its + // full unwrapped height in rows that never show, taking them from the siblings below + // (ktsu-dev/TUI#249, the container route to ktsu-dev/TUI#198). + return Dimensions.Empty.WithPadding(Padding); + } + + Dimensions requiredDimensions = OnCalculateRequiredDimensionsForChildren(availableWidth - Padding.Horizontal); return requiredDimensions.WithPadding(Padding); }