finders_test.dart 6.53 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/rendering.dart';
6 7 8 9 10
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
11 12 13 14 15 16 17
  group('text', () {
    testWidgets('finds Text widgets', (WidgetTester tester) async {
      await tester.pumpWidget(_boilerplate(
        const Text('test'),
      ));
      expect(find.text('test'), findsOneWidget);
    });
18

19 20 21 22
    testWidgets('finds Text.rich widgets', (WidgetTester tester) async {
      await tester.pumpWidget(_boilerplate(
        const Text.rich(
          TextSpan(text: 't', children: <TextSpan>[
23 24
            TextSpan(text: 'e'),
            TextSpan(text: 'st'),
25
          ],
26 27 28 29 30 31 32
        ),
      )));

      expect(find.text('test'), findsOneWidget);
    });
  });

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
  group('textContaining', () {
    testWidgets('finds Text widgets', (WidgetTester tester) async {
      await tester.pumpWidget(_boilerplate(
        const Text('this is a test'),
      ));
      expect(find.textContaining(RegExp(r'test')), findsOneWidget);
      expect(find.textContaining('test'), findsOneWidget);
      expect(find.textContaining('a'), findsOneWidget);
      expect(find.textContaining('s'), findsOneWidget);
    });

    testWidgets('finds Text.rich widgets', (WidgetTester tester) async {
      await tester.pumpWidget(_boilerplate(
          const Text.rich(
            TextSpan(text: 'this', children: <TextSpan>[
              TextSpan(text: 'is'),
              TextSpan(text: 'a'),
              TextSpan(text: 'test'),
            ],
            ),
          )));

      expect(find.textContaining(RegExp(r'isatest')), findsOneWidget);
      expect(find.textContaining('isatest'), findsOneWidget);
    });

    testWidgets('finds EditableText widgets', (WidgetTester tester) async {
      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: _boilerplate(TextField(
            controller: TextEditingController()..text = 'this is test',
          )),
        ),
      ));

      expect(find.textContaining(RegExp(r'test')), findsOneWidget);
      expect(find.textContaining('test'), findsOneWidget);
    });
  });

73 74 75
  group('semantics', () {
    testWidgets('Throws StateError if semantics are not enabled', (WidgetTester tester) async {
      expect(() => find.bySemanticsLabel('Add'), throwsStateError);
76
    }, semanticsEnabled: false);
77 78 79 80 81 82 83

    testWidgets('finds Semantically labeled widgets', (WidgetTester tester) async {
      final SemanticsHandle semanticsHandle = tester.ensureSemantics();
      await tester.pumpWidget(_boilerplate(
        Semantics(
          label: 'Add',
          button: true,
84
          child: const TextButton(
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
            child: Text('+'),
            onPressed: null,
          ),
        ),
      ));
      expect(find.bySemanticsLabel('Add'), findsOneWidget);
      semanticsHandle.dispose();
    });

    testWidgets('finds Semantically labeled widgets by RegExp', (WidgetTester tester) async {
      final SemanticsHandle semanticsHandle = tester.ensureSemantics();
      await tester.pumpWidget(_boilerplate(
        Semantics(
          container: true,
          child: Row(children: const <Widget>[
            Text('Hello'),
            Text('World'),
          ]),
        ),
      ));
      expect(find.bySemanticsLabel('Hello'), findsNothing);
      expect(find.bySemanticsLabel(RegExp(r'^Hello')), findsOneWidget);
      semanticsHandle.dispose();
    });

    testWidgets('finds Semantically labeled widgets without explicit Semantics', (WidgetTester tester) async {
      final SemanticsHandle semanticsHandle = tester.ensureSemantics();
      await tester.pumpWidget(_boilerplate(
        const SimpleCustomSemanticsWidget('Foo')
      ));
      expect(find.bySemanticsLabel('Foo'), findsOneWidget);
      semanticsHandle.dispose();
    });
  });

120 121 122
  group('hitTestable', () {
    testWidgets('excludes non-hit-testable widgets', (WidgetTester tester) async {
      await tester.pumpWidget(
123
        _boilerplate(IndexedStack(
124 125
          sizing: StackFit.expand,
          children: <Widget>[
126
            GestureDetector(
127 128 129 130 131
              key: const ValueKey<int>(0),
              behavior: HitTestBehavior.opaque,
              onTap: () { },
              child: const SizedBox.expand(),
            ),
132
            GestureDetector(
133 134 135 136 137 138 139 140 141
              key: const ValueKey<int>(1),
              behavior: HitTestBehavior.opaque,
              onTap: () { },
              child: const SizedBox.expand(),
            ),
          ],
        )),
      );
      expect(find.byType(GestureDetector), findsNWidgets(2));
142
      final Finder hitTestable = find.byType(GestureDetector).hitTestable(at: Alignment.center);
143 144 145 146
      expect(hitTestable, findsOneWidget);
      expect(tester.widget(hitTestable).key, const ValueKey<int>(0));
    });
  });
147 148

  testWidgets('ChainedFinders chain properly', (WidgetTester tester) async {
149
    final GlobalKey key1 = GlobalKey();
150
    await tester.pumpWidget(
151
      _boilerplate(Column(
152
        children: <Widget>[
153
          Container(
154 155 156
            key: key1,
            child: const Text('1'),
          ),
157
          Container(
158
            child: const Text('2'),
159
          ),
160 161 162 163 164 165 166 167 168 169 170
        ],
      )),
    );

    // Get the text back. By correctly chaining the descendant finder's
    // candidates, it should find 1 instead of 2. If the _LastFinder wasn't
    // correctly chained after the descendant's candidates, the last element
    // with a Text widget would have been 2.
    final Text text = find.descendant(
      of: find.byKey(key1),
      matching: find.byType(Text),
171
    ).last.evaluate().single.widget as Text;
172 173 174

    expect(text.data, '1');
  });
175 176 177
}

Widget _boilerplate(Widget child) {
178
  return Directionality(
179 180 181 182
    textDirection: TextDirection.ltr,
    child: child,
  );
}
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200

class SimpleCustomSemanticsWidget extends LeafRenderObjectWidget {
  const SimpleCustomSemanticsWidget(this.label);

  final String label;

  @override
  RenderObject createRenderObject(BuildContext context) => SimpleCustomSemanticsRenderObject(label);
}

class SimpleCustomSemanticsRenderObject extends RenderBox {
  SimpleCustomSemanticsRenderObject(this.label);

  final String label;

  @override
  bool get sizedByParent => true;

201 202 203 204 205
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return constraints.smallest;
  }

206 207 208 209 210
  @override
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
    config..label = label..textDirection = TextDirection.ltr;
  }
211
}