Skip to content

getSemantics.rect != matchesSemantics.rect  #115079

@TahaTesser

Description

@TahaTesser

I've run into this issue at #114999 (comment).

Description

Complete test code sample

test
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Theme(
          data: ThemeData.light(),
          child: const Directionality(
            textDirection: TextDirection.rtl,
            child: Box(),
          ),
        ),
      ),
    );

    await tester.pumpAndSettle();

    // From `tester.getSemantics(find.byType(Box)).toStringDeep()`
    //
    // SemanticsNode#6
    //  │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
    //  │
    //  ├─SemanticsNode#1
    //  │   Rect.fromLTRB(45.6, 50.1, 601.0, 500.8)
    //  │
    //  └─SemanticsNode#2
    //      Rect.fromLTRB(10.6, 100.1, 701.0, 400.8)`

    final SemanticsNode semanticsNode = tester.getSemantics(find.byType(Box));

    // This fails.
    // expect(
    //   semanticsNode,
    //   matchesSemantics(
    //     children: <Matcher>[
    //       matchesSemantics(
    //         rect: const Rect.fromLTRB(45.6, 50.1, 601.0, 500.8),
    //       ),
    //       matchesSemantics(
    //         rect: const Rect.fromLTRB(10.6, 100.1, 701.0, 400.8),
    //       ),
    //     ],
    //   ),
    // );

    // This woorks.
    // Get semantics node rects.
    final List<Rect> rects = <Rect>[];
    semanticsNode.visitChildren((SemanticsNode node) {
        rects.add(
          Rect.fromLTRB(
            node.rect.left.roundToDouble(),
            node.rect.top.roundToDouble(),
            node.rect.right.roundToDouble(),
            node.rect.bottom.roundToDouble(),
          ),
        );
      return true;
    });
    // Test that the semantics node rect sizes are correct.
    expect(rects, <Rect>[
      const Rect.fromLTRB(46.0, 50.0, 601.0, 501.0),
      const Rect.fromLTRB(11.0, 100.0, 701.0, 401.0)
    ]);
  });
}

class Box extends LeafRenderObjectWidget {
  const Box({super.key});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return _RenderBox(
      textDirection: Directionality.of(context),
    );
  }
}

class _RenderBox extends RenderBox {
  _RenderBox({required TextDirection textDirection})
      : _textDirection = textDirection;

  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    assert(value != null);
    if (value == _textDirection) {
      return;
    }
    _textDirection = value;
  }

  @override
  void paint(PaintingContext context, Offset offset) {}

  @override
  void performLayout() {}

  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return constraints.biggest;
  }

  @override
  bool get sizedByParent => true;

  SemanticsNode? _startSemanticsNode = SemanticsNode();

  SemanticsNode? _endSemanticsNode = SemanticsNode();

  @override
  void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config,
      Iterable<SemanticsNode> children) {
    assert(children.isEmpty);

    const Rect leftRect =
        Rect.fromLTRB(10.568696, 100.12334, 700.98776, 400.7890);
    const Rect rightRect =
        Rect.fromLTRB(45.568696, 50.12334, 600.98776, 500.7890);

    switch (textDirection) {
      case TextDirection.ltr:
        _startSemanticsNode!.rect = leftRect;
        _endSemanticsNode!.rect = rightRect;
        break;
      case TextDirection.rtl:
        _startSemanticsNode!.rect = rightRect;
        _endSemanticsNode!.rect = leftRect;
        break;
    }

    final List<SemanticsNode> finalChildren = <SemanticsNode>[
      _startSemanticsNode!,
      _endSemanticsNode!,
    ];

    node.updateWith(config: config, childrenInInversePaintOrder: finalChildren);
  }

  @override
  void clearSemantics() {
    super.clearSemantics();
    _startSemanticsNode = null;
    _endSemanticsNode = null;
  }

  @override
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
    config.isSemanticBoundary = true;
  }
}

When providing rect from getSemantics to matchesSemantics.rect, the test fails.

    // From `tester.getSemantics(find.byType(Box)).toStringDeep()`
    //
    // SemanticsNode#6
    //  │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
    //  │
    //  ├─SemanticsNode#1
    //  │   Rect.fromLTRB(45.6, 50.1, 601.0, 500.8)
    //  │
    //  └─SemanticsNode#2
    //      Rect.fromLTRB(10.6, 100.1, 701.0, 400.8)

Test:

    expect(
      tester.getSemantics(find.byType(Box)),
      matchesSemantics(
      
        children: <Matcher>[
          matchesSemantics(
            rect: const Rect.fromLTRB(45.6, 50.1, 601.0, 500.8),
          ),
          matchesSemantics(
            rect: const Rect.fromLTRB(10.6, 100.1, 701.0, 400.8),
          ),
        ],
      ),
    );

Result:

 Actual: SemanticsNode:<SemanticsNode#6(Rect.fromLTRB(0.0, 0.0, 800.0, 600.0))>
   Which: rect was: Rect.fromLTRB(45.6, 50.1, 601.0, 500.8)

Investigation:

The match fails when you provide rect size fromgetSemantics to matchesSemantics.rect.
Since the data.rect is not rounded when matching matchesSemantics.rect, this fails.

if (rect != null && rect != data.rect) {
return failWithDescription(matchState, 'rect was: ${data.rect}');
}

Workaround

    // This woorks.
    // Get semantics node rects.
    final List<Rect> rects = <Rect>[];
    semanticsNode.visitChildren((SemanticsNode node) {
        rects.add(
          Rect.fromLTRB(
            node.rect.left.roundToDouble(),
            node.rect.top.roundToDouble(),
            node.rect.right.roundToDouble(),
            node.rect.bottom.roundToDouble(),
          ),
        );
      return true;
    });
    // Test that the semantics node rect sizes are correct.
    expect(rects, <Rect>[
      const Rect.fromLTRB(46.0, 50.0, 601.0, 501.0),
      const Rect.fromLTRB(11.0, 100.0, 701.0, 401.0)
    ]);
  });

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)a: tests"flutter test", flutter_test, or one of our testsframeworkflutter/packages/flutter repository. See also f: labels.team-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions