Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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 lib/web_ui/lib/src/engine/semantics/checkable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class Checkable extends PrimaryRoleManager {
PrimaryRole.checkable,
semanticsObject,
labelRepresentation: LeafLabelRepresentation.ariaLabel,
);
) {
addTappable();
}

final _CheckableKind _kind;

Expand Down
4 changes: 3 additions & 1 deletion lib/web_ui/lib/src/engine/semantics/link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ class Link extends PrimaryRoleManager {
PrimaryRole.link,
semanticsObject,
labelRepresentation: LeafLabelRepresentation.domText,
);
) {
addTappable();
}

@override
DomElement createElement() {
Expand Down
13 changes: 11 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,6 @@ abstract class PrimaryRoleManager {
addLiveRegion();
addRouteName();
addLabelAndValue(labelRepresentation: labelRepresentation);
addTappable();
}

/// Initializes a blank role for a [semanticsObject].
Expand Down Expand Up @@ -625,7 +624,17 @@ final class GenericRole extends PrimaryRoleManager {
PrimaryRole.generic,
semanticsObject,
labelRepresentation: LeafLabelRepresentation.domText,
);
) {
// Typically a tappable widget would have a more specific role, such as
// "link", "button", "checkbox", etc. However, there are situations when a
// tappable is not a leaf node, but contains other nodes, which can also be
// tappable. For example, the dismiss barrier of a pop-up menu is a tappable
// ancestor of the menu itself, while the menu may contain tappable
// children.
if (semanticsObject.isTappable) {
addTappable();
}
}

@override
void update() {
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine/semantics/tappable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Button extends PrimaryRoleManager {
semanticsObject,
labelRepresentation: LeafLabelRepresentation.domText,
) {
addTappable();
setAriaRole('button');
}

Expand Down
71 changes: 71 additions & 0 deletions lib/web_ui/test/engine/semantics/semantics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ void runSemanticsTests() {
group('Role managers', () {
_testRoleManagerLifecycle();
});
group('Text', () {
_testText();
});
group('container', () {
_testContainer();
});
Expand Down Expand Up @@ -718,6 +721,74 @@ void _testLongestIncreasingSubsequence() {
});
}

void _testText() {
test('renders a piece of plain text', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;

final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
updateNode(
builder,
label: 'plain text',
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
);
owner().updateSemantics(builder.build());

expectSemanticsTree(
owner(),
'''<sem role="text" style="$rootSemanticStyle">plain text</sem>''',
);

final SemanticsObject node = owner().debugSemanticsTree![0]!;
expect(node.primaryRole?.role, PrimaryRole.generic);
expect(
node.primaryRole!.secondaryRoleManagers!.map((RoleManager m) => m.runtimeType).toList(),
<Type>[
Focusable,
LiveRegion,
RouteName,
LabelAndValue,
],
);
semantics().semanticsEnabled = false;
});

test('renders a tappable piece of text', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;

final SemanticsTester tester = SemanticsTester(owner());
tester.updateNode(
id: 0,
hasTap: true,
label: 'tappable text',
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
);
tester.apply();

expectSemanticsTree(
owner(),
'''<sem flt-tappable="" role="text" style="$rootSemanticStyle">tappable text</sem>''',
);

final SemanticsObject node = owner().debugSemanticsTree![0]!;
expect(node.primaryRole?.role, PrimaryRole.generic);
expect(
node.primaryRole!.secondaryRoleManagers!.map((RoleManager m) => m.runtimeType).toList(),
<Type>[
Focusable,
LiveRegion,
RouteName,
LabelAndValue,
Tappable,
],
);
semantics().semanticsEnabled = false;
});
}

void _testContainer() {
test('container node has no transform when there is no rect offset',
() async {
Expand Down