Skip to content

Add regression tests for content measured at the unclamped style width - #1095

Open
HelgeSverre wants to merge 2 commits into
DioxusLabs:mainfrom
HelgeSverre:tests/measure-clamped-known-width
Open

Add regression tests for content measured at the unclamped style width#1095
HelgeSverre wants to merge 2 commits into
DioxusLabs:mainfrom
HelgeSverre:tests/measure-clamped-known-width

Conversation

@HelgeSverre

@HelgeSverre HelgeSverre commented Aug 11, 2026

Copy link
Copy Markdown

Objective

Pin down a failure mode that #989 fixed incidentally but left untested: a flex child whose style width resolves wider than its own max_width (width: 100% of a wider parent, or a fixed width above the cap) used to have its content measured at the unclamped width while being laid out at the clamped one. With a measure function whose height depends on its width (wrapped text), the child's flex base size went stale — the child and its ancestors ended up shorter than the content laid out inside them.

On 0.12.2 and earlier this makes downstream UIs paint blocks on top of each other: gpui/Zed's long-standing "overlapping text" bugs (zed-industries/zed#30002, zed-industries/zed#30097, worked around in-app in zed-industries/zed#30377) reduce to exactly this — gpui's virtualized list stacks items by their root height, and a width: 100%; max-width: N readable column inside an item left the root ~1 line short per wrapped paragraph.

Minimal standalone repro — fails on 0.12.2 (`column: 560x40` vs text `560x60`), passes on 0.13.0
use taffy::prelude::*;
use taffy::AvailableSpace;

/// A wrapping text: 1163px long unwrapped, 20px per line.
fn measure_text(known: Size<Option<f32>>, available: Size<AvailableSpace>) -> Size<f32> {
    const TEXT_WIDTH: f32 = 1163.0;
    const LINE_HEIGHT: f32 = 20.0;
    let width = known.width.or(match available.width {
        AvailableSpace::Definite(w) => Some(w),
        _ => None,
    });
    match width {
        Some(w) => {
            let lines = (TEXT_WIDTH / w).ceil().max(1.0);
            Size { width: w.min(TEXT_WIDTH), height: lines * LINE_HEIGHT }
        }
        None => Size { width: TEXT_WIDTH, height: LINE_HEIGHT },
    }
}

fn main() {
    let mut tree: TaffyTree<()> = TaffyTree::new();
    let text = tree.new_leaf_with_context(Style::default(), ()).unwrap();
    let column = tree
        .new_with_children(
            Style {
                flex_direction: FlexDirection::Column,
                size: Size { width: percent(1.0), height: auto() },
                max_size: Size { width: length(560.0), height: auto() },
                ..Default::default()
            },
            &[text],
        )
        .unwrap();
    let root = tree
        .new_with_children(
            Style {
                flex_direction: FlexDirection::Column,
                size: Size { width: length(700.0), height: auto() },
                ..Default::default()
            },
            &[column],
        )
        .unwrap();
    tree.compute_layout_with_measure(root, Size::MAX_CONTENT, |known, available, _, _, _| {
        measure_text(known, available)
    })
    .unwrap();
    let column_size = tree.layout(column).unwrap().size;
    let text_size = tree.layout(text).unwrap().size;
    println!("column: {}x{}", column_size.width, column_size.height);
    println!("text:   {}x{}", text_size.width, text_size.height);
    assert!(column_size.height >= text_size.height, "column is shorter than the text inside it");
}

The clamp added in #989 (motivated by aspect-ratio transferred sizes) also covers plain min/max constraints at the flex-basis site, which fixes this class — but nothing exercised the measure-function/wrapped-text consequence, so it could silently regress.

For conformance context: Chrome lays out the analog DOM (width: 100%; max-width: 560px around wrapping text) with the container containing its wrapped text, and flexbox §9.2 step 3 item E sizes the item at its used cross size (the "min and max sizes are ignored" exemption at flex-basis time is main-axis only).

Changes

Two generated HTML fixtures in test_fixtures/flex, using Ahem text whose line count differs between the unclamped and clamped widths (2 lines at 300px vs 3 lines at 200px):

  • measure_child_with_percent_width_clamped_by_max_widthwidth: 100% of a 300px parent with max-width: 200px; the text and containing column are 200px × 30px.
  • measure_child_with_fixed_width_clamped_by_max_width_intrinsic — the same clamp through the intrinsic-sizing path with a content-sized root.

The generator renders both fixtures in Chrome and emits border-box/content-box and LTR/RTL variants, so all expected layouts are browser-derived.

Test results

  • cargo test --test xml: 5549 passed, 0 failed.
  • cargo test --tests: passed (4 pre-existing ignored tests).
  • cargo +nightly clippy --workspace: passed.
  • Fixture formatting and a second just gentest run produced byte-identical generated output.
  • The percentage-width fixture fails on Taffy 0.12.2 with 20px ancestors around a 30px text leaf, and passes on the PR branch with all three heights at 30px.

@nicoburns nicoburns left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! But could we make these generated tests please? Generated tests automatically assert their layout against Chrome, so don't need to trust/verify their assertions and they are easier to maintain.

See CONTRIBUTING.md

@HelgeSverre

Copy link
Copy Markdown
Author

I think i did it right, let me know if this is what you meant @nicoburns

@nicoburns
nicoburns force-pushed the tests/measure-clamped-known-width branch from e9f6c77 to 39fea26 Compare August 21, 2026 14:58
@nicoburns
nicoburns enabled auto-merge (squash) August 21, 2026 14:58
@HelgeSverre

Copy link
Copy Markdown
Author

@nicoburns feel free to close this if you merged a superseding version in already.

A flex child with a style width that resolves wider than its own
max_width (width: 100% of a wider parent, or a fixed width above the
cap) used to have its content measured at the unclamped width while
being laid out at the clamped one. A measure function whose height
depends on width (wrapped text) then reported a taller size in the
final pass than the height reserved from the sizing pass, and the
child's flex base size went stale: the child and its ancestors ended up
shorter than the content painted inside them.

The clamp added in DioxusLabs#989 (motivated by aspect-ratio transferred sizes)
also fixed this for plain min/max constraints, but left the
measure-function case untested. These tests pin it down with wrapping
Ahem text whose line count differs between the unclamped and clamped
widths, through both the definite-parent path and the intrinsic-sizing
path.
@nicoburns
nicoburns force-pushed the tests/measure-clamped-known-width branch from c7d7fcf to ccfd425 Compare August 25, 2026 15:52
@nicoburns

Copy link
Copy Markdown
Member

Ack, I thought I had merged this, but I guess the auto-merge never triggered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants