Skip to content

[iOS] Fix Label with TailTruncation not rendering after empty-to-non-empty text transition#34812

Merged
kubaflo merged 1 commit intoinflight/currentfrom
fix-34591-inflight-current
Apr 4, 2026
Merged

[iOS] Fix Label with TailTruncation not rendering after empty-to-non-empty text transition#34812
kubaflo merged 1 commit intoinflight/currentfrom
fix-34591-inflight-current

Conversation

@kubaflo
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo commented Apr 4, 2026

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Context

This is a retarget of #34698 from inflight/candidate to inflight/current. The candidate branch has already been stabilized and merging additional changes there could introduce new test failures.

Issue Details

  • When a Label is initialized with LineBreakMode="TailTruncation" and its Text is null or empty during the first render, later updates to the Text property do not render any visible text on iOS. The label remains visually empty even though the Text value changes successfully.

Root Cause

  • Regression: Introduced by PR Compute LayoutConstraints on new *StackLayout and Grid #28931
  • PR Compute LayoutConstraints on new *StackLayout and Grid #28931 introduced ComputeConstraintForView on several layouts, which causes a Label with default HorizontalOptions = Fill (e.g., inside a VerticalStackLayout) to be marked as HorizontallyFixed. This activates an existing optimization in TextChangedShouldInvalidateMeasure that skips InvalidateMeasureInternal for labels that are both single-line and horizontally fixed.
  • This optimization is valid for typical text changes (e.g., "Hello" → "World") where the height remains unchanged. However, it fails for the empty → non-empty transition. When a label initially has null/empty text, iOS measures it with a height of 0, and MAUI caches this value. When text is later assigned, the optimization prevents re-measurement, leaving the stale 0-height intact—resulting in the label being invisible despite having text.

Description of Change

  • Updated OnTextPropertyChanged in Label.cs to always invalidate measure when the label's text transitions between empty and non-empty, ensuring the label is properly rendered and sized in scenarios like TailTruncation.

Issues Fixed

Fixes #34591

Original PR

Cherry-pick of #34698 by @SyedAbdulAzeemSF4852

…empty text transition

Fixes #34591

Cherry-pick of #34698 retargeted from inflight/candidate to inflight/current.

Co-authored-by: SyedAbdulAzeemSF4852 <184361905+SyedAbdulAzeemSF4852@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo kubaflo merged commit befa700 into inflight/current Apr 4, 2026
5 of 14 checks passed
@kubaflo kubaflo deleted the fix-34591-inflight-current branch April 4, 2026 10:11
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

🧪 PR Test Evaluation

Overall Verdict: ✅ Tests are adequate

The test directly exercises the exact bug scenario — a Label with TailTruncation starting with null text and then receiving non-null text — and asserts on the concrete observable symptom (non-zero height after update).

👍 / 👎 — Was this evaluation helpful? React to let us know!

📊 Expand Full Evaluation

PR Test Evaluation Report

PR: #34812 — [iOS] Fix Label with TailTruncation not rendering after empty-to-non-empty text transition
Test files evaluated: 2 (Issue34591.cs in HostApp + Shared.Tests)
Fix files: 1 (src/Controls/src/Core/Label/Label.cs)


Overall Verdict

✅ Tests are adequate

The test correctly targets the reported failure scenario and directly verifies the observable outcome of the fix.


1. Fix Coverage — ✅

The fix adds a new condition in TextPropertyChanged:

if (TextChangedShouldInvalidateMeasure(label) || (wasEmpty != isEmpty && IsLabelSizeable(label)))
    label.InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);

The test creates a Label with LineBreakMode.TailTruncation and no initial text (null), then taps a button that sets resultLabel.Text = "Test", and asserts rect.Height > 0. This precisely exercises the newly added wasEmpty != isEmpty branch — without the fix, TextChangedShouldInvalidateMeasure would return false for a single-line horizontally-fixed label, causing the label to keep zero height. ✅ The test would fail if the fix were reverted.


2. Edge Cases & Gaps — ⚠️

Covered:

  • null → non-empty text transition (the reported bug case)

Missing (minor):

  • "" (empty string) → non-empty transition — the fix handles string.IsNullOrEmpty, so both null and "" trigger the new path, but only null is tested
  • non-empty → null/empty transition (reverse direction) — would the label return to zero height? The fix also applies to this direction (wasEmpty != isEmpty), but it's untested

These gaps are minor: the primary bug scenario is well-covered, and the reverse transitions are lower-priority follow-ons.


3. Test Type Appropriateness — ✅

Current: UI Test (Appium, TestCases.Shared.Tests)
Recommendation: Same — UI test is justified here.

The bug is a visual rendering failure: after setting text, the label height remains 0 on screen. This requires a native layout pass to verify, which is not easily testable at the unit test level without mocking the entire layout engine. A device test could verify the handler calls InvalidateMeasureInternal, but that would be testing implementation details. Measuring rect.Height via Appium is the most direct verification of the fix's observable effect.


4. Convention Compliance — ✅

All new test files follow conventions correctly:

  • Naming: Issue34591.cs matches the IssueXXXXX pattern
  • [Issue()] attribute: Present on HostApp page with correct tracker, number, description, and PlatformAffected.iOS
  • [Category(UITestCategories.Label)]: Exactly one category attribute on the test method
  • Base class: _IssuesUITest in the shared test; TestContentPage in HostApp (appropriate pattern for Init())
  • WaitForElement before interaction: App.WaitForElement("UpdateTextButton") precedes App.Tap("UpdateTextButton")
  • No Task.Delay/Thread.Sleep
  • No inline #if directives
  • No obsolete APIs (Application.MainPage, Frame, etc.)
  • C# only for the HostApp page (XAML not needed here)

Note: The script reported 168 total convention issues, but these are all in pre-existing test files unrelated to this PR's changes.


5. Flakiness Risk — ✅ Low

  • App.WaitForElement("UpdateTextButton") provides proper synchronization before the tap
  • App.WaitForElement("ResultLabel") ensures the element is present before calling GetRect()
  • No screenshot assertions (no cursor-blink risk)
  • No animation involved — the transition is a simple property change
  • No Task.Delay or Thread.Sleep
  • No external dependencies

6. Duplicate Coverage — ✅ No duplicates

Existing MauiLabel tests cover unrelated scenarios (e.g., Issue18775, Issue27992, Issue29233, Issue29882). None test the TailTruncation + empty→non-empty text transition. No consolidation needed.


7. Platform Scope — ✅

The [Issue] attribute declares PlatformAffected.iOS, correctly reflecting where the bug was reported. The fix is in cross-platform Label.cs, so it benefits all platforms, but the visible failure was iOS-specific. The test itself will run on all platforms (no skip annotation), which is fine — other platforms should pass cleanly with or without the fix.


8. Assertion Quality — ✅

Assert.That(rect.Height, Is.GreaterThan(0),
    "Label should have non-zero height after setting text from null");
  • Specific: Directly measures the symptom of the bug (height = 0 without fix, > 0 with fix)
  • Not brittle: > 0 rather than a magic pixel value, so it's stable across devices and font sizes
  • Descriptive message: Explains what the assertion is testing

9. Fix-Test Alignment — ✅

Fix Test
Label.cs — measure invalidation for empty↔non-empty transitions Creates Label with TailTruncation + null text, taps button to set text, checks non-zero height
Targets IsLabelSizeable labels in VerticalStackLayout (vertically sizable, horizontally fixed) Uses VerticalStackLayout, so label is exactly horizontally fixed + vertically sizable

Perfect alignment between fix and test.


Recommendations

  1. (Optional, low-priority) Add a test step for the "" → "Test" transition to cover the string.IsNullOrEmpty("") path explicitly, though this is not blocking.
  2. (Optional) Consider verifying that the label also reverts to zero height (or near-zero) when text is cleared back to null/empty, to guard against future regressions in that direction.

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • dc.services.visualstudio.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "dc.services.visualstudio.com"

See Network Configuration for more information.

Note

🔒 Integrity filtering filtered 1 item

Integrity filtering activated and filtered the following item during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.

🧪 Test evaluation by Evaluate PR Tests

PureWeen pushed a commit that referenced this pull request Apr 8, 2026
…empty text transition (#34812)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Context

This is a retarget of #34698 from `inflight/candidate` to
`inflight/current`. The candidate branch has already been stabilized and
merging additional changes there could introduce new test failures.

### Issue Details
- When a Label is initialized with LineBreakMode="TailTruncation" and
its Text is null or empty during the first render, later updates to the
Text property do not render any visible text on iOS. The label remains
visually empty even though the Text value changes successfully.

### Root Cause
- **Regression**: Introduced by PR #28931
- PR #28931 introduced ComputeConstraintForView on several layouts,
which causes a Label with default HorizontalOptions = Fill (e.g., inside
a VerticalStackLayout) to be marked as HorizontallyFixed. This activates
an existing optimization in TextChangedShouldInvalidateMeasure that
skips InvalidateMeasureInternal for labels that are both single-line and
horizontally fixed.
- This optimization is valid for typical text changes (e.g., "Hello" →
"World") where the height remains unchanged. However, it fails for the
empty → non-empty transition. When a label initially has null/empty
text, iOS measures it with a height of 0, and MAUI caches this value.
When text is later assigned, the optimization prevents re-measurement,
leaving the stale 0-height intact—resulting in the label being invisible
despite having text.

### Description of Change
- Updated OnTextPropertyChanged in Label.cs to always invalidate measure
when the label's text transitions between empty and non-empty, ensuring
the label is properly rendered and sized in scenarios like
TailTruncation.

### Issues Fixed
Fixes #34591

### Original PR
Cherry-pick of #34698 by @SyedAbdulAzeemSF4852

Co-authored-by: SyedAbdulAzeemSF4852 <184361905+SyedAbdulAzeemSF4852@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant