evaluate method

  1. @override
Future<Evaluation> evaluate (WidgetTester tester)
override

Evaluate whether the current state of the tester conforms to the rule.

Implementation

@override
Future<Evaluation> evaluate(WidgetTester tester) async {
  final SemanticsNode root = tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;
  final RenderView renderView = tester.binding.renderView;
  final OffsetLayer layer = renderView.layer;
  ui.Image image;
  final ByteData byteData = await tester.binding.runAsync<ByteData>(() async {
    // Needs to be the same pixel ratio otherwise our dimensions won't match the
    // last transform layer.
    image = await layer.toImage(renderView.paintBounds, pixelRatio: 1 / 3);
    return image.toByteData();
  });

  Future<Evaluation> evaluateNode(SemanticsNode node) async {
    Evaluation result = const Evaluation.pass();
    if (node.isInvisible || node.isMergedIntoParent || node.hasFlag(ui.SemanticsFlag.isHidden))
      return result;
    final SemanticsData data = node.getSemanticsData();
    final List<SemanticsNode> children = <SemanticsNode>[];
    node.visitChildren((SemanticsNode child) {
      children.add(child);
      return true;
    });
    for (SemanticsNode child in children) {
      result += await evaluateNode(child);
    }
    if (_shouldSkipNode(data)) {
      return result;
    }

    // We need to look up the inherited text properties to determine the
    // contrast ratio based on text size/weight.
    double fontSize;
    bool isBold;
    final String text = (data.label?.isEmpty == true) ? data.value : data.label;
    final List<Element> elements = find.text(text).hitTestable().evaluate().toList();
    Rect paintBounds;
    if (elements.length == 1) {
      final Element element = elements.single;
      final RenderBox renderObject = element.renderObject;
      element.renderObject.paintBounds;
      paintBounds = Rect.fromPoints(
        renderObject.localToGlobal(element.renderObject.paintBounds.topLeft - const Offset(4.0, 4.0)),
        renderObject.localToGlobal(element.renderObject.paintBounds.bottomRight + const Offset(4.0, 4.0)),
      );
      final Widget widget = element.widget;
      final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(element);
      if (widget is Text) {
        TextStyle effectiveTextStyle = widget.style;
        if (widget.style == null || widget.style.inherit) {
          effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
        }
        fontSize = effectiveTextStyle.fontSize;
        isBold = effectiveTextStyle.fontWeight == FontWeight.bold;
      } else if (widget is EditableText) {
        isBold = widget.style.fontWeight == FontWeight.bold;
        fontSize = widget.style.fontSize;
      } else {
        assert(false);
      }
    } else if (elements.length > 1) {
      return Evaluation.fail('Multiple nodes with the same label: ${data.label}\n');
    } else {
      // If we can't find the text node then assume the label does not
      // correspond to actual text.
      return result;
    }

    if (_isNodeOffScreen(paintBounds)) {
      return result;
    }
    final List<int> subset = _subsetToRect(byteData, paintBounds, image.width, image.height);
    // Node was too far off screen.
    if (subset.isEmpty) {
      return result;
    }
    final _ContrastReport report = _ContrastReport(subset);
    final double contrastRatio = report.contrastRatio();
    const double delta = -0.01;
    double targetContrastRatio;
    if ((isBold && fontSize > kBoldTextMinimumSize) || (fontSize ?? 12.0) > kLargeTextMinimumSize) {
      targetContrastRatio = kMinimumRatioLargeText;
    } else {
      targetContrastRatio = kMinimumRatioNormalText;
    }
    if (contrastRatio - targetContrastRatio >= delta)
      return result + const Evaluation.pass();
    return result + Evaluation.fail(
      '$node:\nExpected contrast ratio of at least '
      '$targetContrastRatio but found ${contrastRatio.toStringAsFixed(2)} for a font size of $fontSize. '
      'The computed foreground color was: ${report.lightColor}, '
      'The computed background color was: ${report.darkColor}\n'
      'See also: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html'
    );
  }
  return evaluateNode(root);
}