semantics_tester.dart 5.93 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:meta/meta.dart';

export 'package:flutter/rendering.dart' show SemanticsData;

/// Test semantics data that is compared against real semantics tree.
///
/// Useful with [hasSemantics] and [SemanticsTester] to test the contents of the
/// semantics tree.
class TestSemantics {
  /// Creates an object witht some test semantics data.
  ///
  /// If [rect] argument is null, the [rect] field with ve initialized with
  /// `new Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)`, which is the default size of
  /// the screen during unit testing.
  TestSemantics({
    this.id,
    this.flags: 0,
    this.actions: 0,
    this.label: '',
    Rect rect,
    this.transform,
    this.children: const <TestSemantics>[],
  }) : rect = rect ?? new Rect.fromLTRB(0.0, 0.0, 800.0, 600.0);

  /// The unique identifier for this node.
  ///
  /// The root node has an id of zero. Other nodes are given a unique id when
  /// they are created.
  final int id;

  /// A bit field of [SemanticsFlags] that apply to this node.
  final int flags;

  /// A bit field of [SemanticsActions] that apply to this node.
  final int actions;

  /// A textual description of this node.
  final String label;

  /// The bounding box for this node in its coordinate system.
  final Rect rect;

  /// The transform from this node's coordinate system to its parent's coordinate system.
  ///
  /// By default, the transform is null, which represents the identity
  /// transformation (i.e., that this node has the same coorinate system as its
  /// parent).
  final Matrix4 transform;

  /// The children of this node.
  final List<TestSemantics> children;

  SemanticsData _getSemanticsData() {
    return new SemanticsData(
      flags: flags,
      actions: actions,
      label: label,
      rect: rect,
      transform: transform
    );
  }

  bool _matches(SemanticsNode node, Map<dynamic, dynamic> matchState) {
    if (node == null || id != node.id
        || _getSemanticsData() != node.getSemanticsData()
        || children.length != (node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount)) {
      matchState[TestSemantics] = this;
      matchState[SemanticsNode] = node;
      return false;
    }
    if (children.isEmpty)
      return true;
    bool result = true;
    Iterator<TestSemantics> it = children.iterator;
    node.visitChildren((SemanticsNode node) {
      it.moveNext();
      if (!it.current._matches(node, matchState)) {
        result = false;
        return false;
      }
      return true;
    });
    return result;
  }
}

/// Ensures that the given widget tester has a semantics tree to test.
///
/// Useful with [hasSemantics] to test the contents of the semantics tree.
class SemanticsTester {
  /// Creates a semantics tester for the given widget tester.
  ///
  /// You should call [dispose] at the end of a test that creates a semantics
  /// tester.
  SemanticsTester(this.tester) {
    _semanticsHandle = tester.binding.pipelineOwner.ensureSemantics();
  }

  /// The widget tester that this object is testing the semantics of.
  final WidgetTester tester;
  SemanticsHandle _semanticsHandle;

  /// Release resources held by this semantics tester.
  ///
  /// Call this function at the end of any test that uses a semantics tester.
  @mustCallSuper
  void dispose() {
    _semanticsHandle.dispose();
    _semanticsHandle = null;
  }

  @override
  String toString() => 'SemanticsTester';
}

class _HasSemantics extends Matcher {
  const _HasSemantics(this._semantics);

  final TestSemantics _semantics;

  @override
  bool matches(@checked SemanticsTester item, Map<dynamic, dynamic> matchState) {
    return _semantics._matches(item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode, matchState);
  }

  @override
  Description describe(Description description) {
    return description.add('semantics node id ${_semantics.id}');
  }

  @override
  Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
    TestSemantics testNode = matchState[TestSemantics];
    SemanticsNode node = matchState[SemanticsNode];
    if (node == null)
      return mismatchDescription.add('could not find node with id ${testNode.id}');
    if (testNode.id != node.id)
      return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}');
    final SemanticsData data = node.getSemanticsData();
    if (testNode.flags != data.flags)
      return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}');
    if (testNode.actions != data.actions)
      return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}');
    if (testNode.label != data.label)
      return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}"');
    if (testNode.rect != data.rect)
      return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}');
    if (testNode.transform != data.transform)
      return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform ${data.transform}');
    final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount;
    if (testNode.children.length != childrenCount)
      return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} but found $childrenCount children');
    return mismatchDescription;
  }
}

/// Asserts that a [SemanticsTester] has a semantics tree that exactly matches the given semantics.
Matcher hasSemantics(TestSemantics semantics) => new _HasSemantics(semantics);