Skip to content

fix: resolve TestSemantics comparison and assert issues#185921

Closed
harshyadavDeveloper wants to merge 3 commits into
flutter:masterfrom
harshyadavDeveloper:fix-test-semantics-bugs
Closed

fix: resolve TestSemantics comparison and assert issues#185921
harshyadavDeveloper wants to merge 3 commits into
flutter:masterfrom
harshyadavDeveloper:fix-test-semantics-bugs

Conversation

@harshyadavDeveloper

@harshyadavDeveloper harshyadavDeveloper commented May 2, 2026

Copy link
Copy Markdown

Description

Fixes multiple issues in TestSemantics where incorrect comparisons
and an invalid assert condition resulted in unintended behavior.

Changes include:

  • Corrected comparison of LocaleStringAttribute.locale to avoid
    comparing a value with itself
  • Fixed incorrect comparison of controlsNodes with itself
  • Updated assert statement to properly combine conditions using &&
    instead of passing multiple arguments

These fixes improve correctness and prevent logical errors in
semantics testing.

Issues fixed by this PR

Fixes #185900

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@flutter-dashboard

Copy link
Copy Markdown

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions Bot added framework flutter/packages/flutter repository. See also f: labels. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) labels May 2, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request corrects tautological comparisons in semantics_tester.dart and modifies an assert statement in the _IncludesNodeWith matcher. Feedback identifies a typo and redundant check in the controlsNodes logic, and highlights a regression in the assert condition that incorrectly requires both property and range values, alongside a violation of the dart format style requirement.

}

if (controlsNodes != controlsNodes && !setEquals(controlsNodes, node.controlsNodes)) {
if (controlsNodes != node.controlsNode && !setEquals(controlsNodes, node.controlsNodes)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

There is a typo in the property name: it should be node.controlsNodes (plural), not node.controlsNode. Additionally, the identity check controlsNodes != node.controlsNodes is redundant because setEquals already performs an identity check internally and handles null values correctly.

Suggested change
if (controlsNodes != node.controlsNode && !setEquals(controlsNodes, node.controlsNodes)) {
if (!setEquals(controlsNodes, node.controlsNodes)) {

Comment on lines +1170 to +1185
(label != null ||
value != null ||
actions != null ||
flags != null ||
flagsCollection != null ||
tags != null ||
increasedValue != null ||
decreasedValue != null ||
scrollPosition != null ||
scrollExtentMax != null ||
scrollExtentMin != null ||
maxValueLength != null ||
currentValueLength != null ||
inputType != null) &&
(minValue != null || maxValue != null),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The logic in the assert statement has been changed from "at least one property" to "at least one property from the first group AND one of the min/max values". This is a regression that will break tests matching only on labels or values. It should use || to ensure at least one of any property is provided. Also, the formatting should follow dart format as per the repository style guide.

Suggested change
(label != null ||
value != null ||
actions != null ||
flags != null ||
flagsCollection != null ||
tags != null ||
increasedValue != null ||
decreasedValue != null ||
scrollPosition != null ||
scrollExtentMax != null ||
scrollExtentMin != null ||
maxValueLength != null ||
currentValueLength != null ||
inputType != null) &&
(minValue != null || maxValue != null),
);
label != null ||
value != null ||
actions != null ||
flags != null ||
flagsCollection != null ||
tags != null ||
increasedValue != null ||
decreasedValue != null ||
scrollPosition != null ||
scrollExtentMax != null ||
scrollExtentMin != null ||
maxValueLength != null ||
currentValueLength != null ||
inputType != null ||
minValue != null ||
maxValue != null,
);
References
  1. All Dart code is formatted using dart format. This is enforced by CI. (link)

@Istiak-Ahmed78 Istiak-Ahmed78 May 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The gemini bots suggestion sounds valid

@harshyadavDeveloper

Copy link
Copy Markdown
Author

Added tests to cover the TestSemantics fixes. Verified that the updated tests pass locally.

);
semantics.dispose();
});

@Istiak-Ahmed78 Istiak-Ahmed78 May 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @harshyadavDeveloper , your tests require fixes. the tests you wrote honestly does not test the comparison errors we are addressing.

locale: const Locale('fr'),
);

expect(first, isNot(equals(second)));

@Istiak-Ahmed78 Istiak-Ahmed78 May 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your tests compares two TestSemantics objects using equals(), but the bugs are in _stringAttributesEqual() & _matches() of the TestSemantics class.
Let me tell me tell you what you are doing:

  • Creating two TestSemantics objects
  • Comparing them using equals()
  • Checks they're NOT equal

This test only compare two TestSemantics objects. Don't test the actual bugges. equals(second) calls the == operator. But TestSemantics does NOT override ==, So it uses the default Dart ==, Test PASSES

controlsNodes: <String>{'3', '4'},
);

expect(first, isNot(equals(second)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same happens here!!

Comment on lines +153 to +181
testWidgets('TestSemantics detects different locales correctly', (WidgetTester tester) async {
final first = TestSemantics(
id: 1,
label: 'Hello',
locale: const Locale('en'),
);

final second = TestSemantics(
id: 1,
label: 'Hello',
locale: const Locale('fr'),
);

expect(first, isNot(equals(second)));
});

testWidgets('TestSemantics detects different controlNodes correctly', (WidgetTester tester) async {
final first = TestSemantics(
id: 1,
controlsNodes: <String>{'1', '2'},
);

final second = TestSemantics(
id: 1,
controlsNodes: <String>{'3', '4'},
);

expect(first, isNot(equals(second)));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For verification of the concept. I ran your test code in my IDE with bugs in semantics_tester.dart. All test PASSED!! However, I'm providing you correct test code. I ensured all these tests fails with buggy behaviour and passes all when the issues are fixed.

Suggested change
testWidgets('TestSemantics detects different locales correctly', (WidgetTester tester) async {
final first = TestSemantics(
id: 1,
label: 'Hello',
locale: const Locale('en'),
);
final second = TestSemantics(
id: 1,
label: 'Hello',
locale: const Locale('fr'),
);
expect(first, isNot(equals(second)));
});
testWidgets('TestSemantics detects different controlNodes correctly', (WidgetTester tester) async {
final first = TestSemantics(
id: 1,
controlsNodes: <String>{'1', '2'},
);
final second = TestSemantics(
id: 1,
controlsNodes: <String>{'3', '4'},
);
expect(first, isNot(equals(second)));
});
testWidgets('hasSemantics should detect controlsNodes mismatch', (WidgetTester tester) async {
final semantics = SemanticsTester(tester);
await tester.pumpWidget(
Semantics(label: 'Test', textDirection: TextDirection.ltr, child: Container()),
);
// Test expects controlsNodes but widget doesn't have any
final testSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: 1,
label: 'Test',
rect: TestSemantics.fullScreen,
controlsNodes: <String>{'node1'},
),
],
);
expect(semantics, isNot(hasSemantics(testSemantics, ignoreId: true)));
semantics.dispose();
});
test('includesNodeWith accepts minValue only', () {
expect(() => includesNodeWith(minValue: '0'), returnsNormally);
});
test('includesNodeWith accepts maxValue only', () {
expect(() => includesNodeWith(maxValue: '100'), returnsNormally);
});
test('includesNodeWith rejects no properties', () {
expect(() => includesNodeWith(), throwsAssertionError);
});
testWidgets('nodesWith detects locale attribute mismatch', (WidgetTester tester) async {
final semantics = SemanticsTester(tester);
await tester.pumpWidget(
const Text.rich(
TextSpan(text: 'Hello', locale: Locale('en')),
textDirection: TextDirection.ltr,
),
);
final attributedLabel = AttributedString(
'Hello',
attributes: [
LocaleStringAttribute(range: const TextRange(start: 0, end: 5), locale: const Locale('fr')),
],
);
final Iterable<SemanticsNode> nodes = semantics.nodesWith(attributedLabel: attributedLabel);
expect(nodes, isEmpty);
semantics.dispose();
});

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! +1 to @Istiak-Ahmed78's points here.

@chunhtai

Copy link
Copy Markdown
Contributor

closing this pr in favor of #185975

@chunhtai chunhtai closed this May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TestSemantics typo bugs

5 participants