Skip to content
Merged
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: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
## 4.17.0
## [next]


- refactor: Flutter 3.47.1 support
- refactor: migrate Material UI imports to the standalone `material_ui` package
- refactor: migrate Cupertino UI imports to the standalone `cupertino_ui` package
- refactor: update localization delegates to use `GlobalMaterialLocalizations.delegates` with the new standalone UI packages
- fix: skip hidden `TitleBar` children when collecting semantics ([#1354](https://github.com/bdlukaa/fluent_ui/pull/1354))

## 4.16.1

Expand Down
19 changes: 19 additions & 0 deletions lib/src/controls/navigation/navigation_view/title_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,25 @@ class _RenderTitleSubtitleOverflow extends RenderBox
child = childAfter(child);
}
}

/// Skips hidden children when collecting semantics.
///
/// Hidden children are never laid out (see [performLayout]). If a semantics
/// walk visits a hidden child before it is laid out, Flutter asserts on
/// `_needsLayout`. Minimizing a maximized window with a screen reader
/// running triggers the assert.
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
var child = firstChild;
while (child != null) {
final childParentData =
child.parentData! as _TitleSubtitleOverflowParentData;
if (!childParentData.isHidden) {
visitor(child);
}
child = childAfter(child);
}
}
}

/// The preferred position for the pane toggle button.
Expand Down
82 changes: 82 additions & 0 deletions test/title_bar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,86 @@ void main() {
// Null titleBar should return 0
expect(TitleBar.calculateHeight(ctx2, null), 0);
});

testWidgets('an initially hidden title has no semantics until it fits', (
tester,
) async {
final handle = tester.ensureSemantics();
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(200, 600);
addTearDown(tester.view.reset);
const searchLabel = 'Search settings';

try {
await tester.pumpWidget(
FluentApp(
home: NavigationView(
titleBar: TitleBar(
isBackButtonVisible: false,
title: Semantics(
label: searchLabel,
button: true,
child: const SizedBox(
width: searchLabel.length * 16,
height: 32,
),
),
),
content: const SizedBox.shrink(),
),
),
);
expect(tester.takeException(), isNull);
expect(find.bySemanticsLabel(searchLabel), findsNothing);

tester.view.physicalSize = const Size(800, 600);
await tester.pump();
expect(tester.takeException(), isNull);
expect(find.bySemanticsLabel(searchLabel), findsOneWidget);
} finally {
handle.dispose();
}
});

testWidgets(
'a dirty title can be hidden and restored with semantics enabled',
(tester) async {
final handle = tester.ensureSemantics();
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(800, 600);
addTearDown(tester.view.reset);

FluentApp buildApp(String title) => FluentApp(
home: NavigationView(
titleBar: TitleBar(
isBackButtonVisible: false,
title: Semantics(
label: title,
button: true,
child: SizedBox(width: title.length * 16, height: 32),
),
),
content: const SizedBox.shrink(),
),
);

try {
await tester.pumpWidget(buildApp('Search'));
expect(tester.takeException(), isNull);
expect(find.bySemanticsLabel('Search'), findsOneWidget);

tester.view.physicalSize = const Size(200, 600);
await tester.pumpWidget(buildApp('Search settings'));
expect(tester.takeException(), isNull);
expect(find.bySemanticsLabel('Search settings'), findsNothing);

tester.view.physicalSize = const Size(800, 600);
await tester.pump();
expect(tester.takeException(), isNull);
expect(find.bySemanticsLabel('Search settings'), findsOneWidget);
} finally {
handle.dispose();
}
},
);
}