basic_test.dart 9 KB
Newer Older
1 2 3 4
// Copyright 2017 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.

5
import 'dart:math' as math;
6
import 'package:flutter_test/flutter_test.dart';
7
import 'package:flutter/material.dart';
8 9 10 11 12 13 14 15
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';

void main() {
  group('PhysicalShape', () {
    testWidgets('properties', (WidgetTester tester) async {
      await tester.pumpWidget(
        const PhysicalShape(
16
          clipper: ShapeBorderClipper(shape: CircleBorder()),
17
          elevation: 2.0,
18 19
          color: Color(0xFF0000FF),
          shadowColor: Color(0xFF00FF00),
20 21 22
        )
      );
      final RenderPhysicalShape renderObject = tester.renderObject(find.byType(PhysicalShape));
23
      expect(renderObject.clipper, const ShapeBorderClipper(shape: CircleBorder()));
24 25 26 27 28 29 30
      expect(renderObject.color, const Color(0xFF0000FF));
      expect(renderObject.shadowColor, const Color(0xFF00FF00));
      expect(renderObject.elevation, 2.0);
    });

    testWidgets('hit test', (WidgetTester tester) async {
      await tester.pumpWidget(
31
        PhysicalShape(
32
          clipper: const ShapeBorderClipper(shape: CircleBorder()),
33 34 35
          elevation: 2.0,
          color: const Color(0xFF0000FF),
          shadowColor: const Color(0xFF00FF00),
36
          child: Container(color: const Color(0xFF0000FF)),
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
        )
      );

      final RenderPhysicalShape renderPhysicalShape =
        tester.renderObject(find.byType(PhysicalShape));

      // The viewport is 800x600, the CircleBorder is centered and fits
      // the shortest edge, so we get a circle of radius 300, centered at
      // (400, 300).
      //
      // We test by sampling a few points around the left-most point of the
      // circle (100, 300).

      expect(tester.hitTestOnBinding(const Offset(99.0, 300.0)), doesNotHit(renderPhysicalShape));
      expect(tester.hitTestOnBinding(const Offset(100.0, 300.0)), hits(renderPhysicalShape));
      expect(tester.hitTestOnBinding(const Offset(100.0, 299.0)), doesNotHit(renderPhysicalShape));
      expect(tester.hitTestOnBinding(const Offset(100.0, 301.0)), doesNotHit(renderPhysicalShape));
54
    }, skip: isBrowser);
55 56 57

  });

Emmanuel Garcia's avatar
Emmanuel Garcia committed
58 59
  group('FractionalTranslation', () {
    testWidgets('hit test - entirely inside the bounding box', (WidgetTester tester) async {
60
      final GlobalKey key1 = GlobalKey();
Emmanuel Garcia's avatar
Emmanuel Garcia committed
61 62 63
      bool _pointerDown = false;

      await tester.pumpWidget(
64 65
        Center(
          child: FractionalTranslation(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
66 67
            translation: Offset.zero,
            transformHitTests: true,
68
            child: Listener(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
69 70 71
              onPointerDown: (PointerDownEvent event) {
                _pointerDown = true;
              },
72
              child: SizedBox(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
73 74 75
                key: key1,
                width: 100.0,
                height: 100.0,
76
                child: Container(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
77 78 79
                  color: const Color(0xFF0000FF)
                ),
              ),
80 81
            ),
          ),
Emmanuel Garcia's avatar
Emmanuel Garcia committed
82 83 84 85 86 87 88 89
        )
      );
      expect(_pointerDown, isFalse);
      await tester.tap(find.byKey(key1));
      expect(_pointerDown, isTrue);
    });

    testWidgets('hit test - partially inside the bounding box', (WidgetTester tester) async {
90
      final GlobalKey key1 = GlobalKey();
Emmanuel Garcia's avatar
Emmanuel Garcia committed
91 92 93
      bool _pointerDown = false;

      await tester.pumpWidget(
94 95
        Center(
          child: FractionalTranslation(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
96 97
            translation: const Offset(0.5, 0.5),
            transformHitTests: true,
98
            child: Listener(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
99 100 101
              onPointerDown: (PointerDownEvent event) {
                _pointerDown = true;
              },
102
              child: SizedBox(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
103 104 105
                key: key1,
                width: 100.0,
                height: 100.0,
106
                child: Container(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
107 108 109
                  color: const Color(0xFF0000FF)
                ),
              ),
110 111
            ),
          ),
Emmanuel Garcia's avatar
Emmanuel Garcia committed
112 113 114 115 116 117 118 119
        )
      );
      expect(_pointerDown, isFalse);
      await tester.tap(find.byKey(key1));
      expect(_pointerDown, isTrue);
    });

    testWidgets('hit test - completely outside the bounding box', (WidgetTester tester) async {
120
      final GlobalKey key1 = GlobalKey();
Emmanuel Garcia's avatar
Emmanuel Garcia committed
121 122 123
      bool _pointerDown = false;

      await tester.pumpWidget(
124 125
        Center(
          child: FractionalTranslation(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
126 127
            translation: const Offset(1.0, 1.0),
            transformHitTests: true,
128
            child: Listener(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
129 130 131
              onPointerDown: (PointerDownEvent event) {
                _pointerDown = true;
              },
132
              child: SizedBox(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
133 134 135
                key: key1,
                width: 100.0,
                height: 100.0,
136
                child: Container(
Emmanuel Garcia's avatar
Emmanuel Garcia committed
137 138 139
                  color: const Color(0xFF0000FF)
                ),
              ),
140 141
            ),
          ),
Emmanuel Garcia's avatar
Emmanuel Garcia committed
142 143 144 145 146 147 148
        )
      );
      expect(_pointerDown, isFalse);
      await tester.tap(find.byKey(key1));
      expect(_pointerDown, isTrue);
    });
  });
149

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
  group('Row', () {
    testWidgets('multiple baseline aligned children', (WidgetTester tester) async {
      final UniqueKey key1 = UniqueKey();
      final UniqueKey key2 = UniqueKey();
      const double fontSize1 = 54;
      const double fontSize2 = 14;

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: Container(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.baseline,
                textBaseline: TextBaseline.alphabetic,
                children: <Widget>[
                  Text('big text',
                    key: key1,
                    style: const TextStyle(fontSize: fontSize1),
                  ),
                  Text('one\ntwo\nthree\nfour\nfive\nsix\nseven',
                    key: key2,
                    style: const TextStyle(fontSize: fontSize2)
                  ),
                ],
              ),
            ),
          ),
        ),
      );

      final RenderBox textBox1 = tester.renderObject(find.byKey(key1));
      final RenderBox textBox2 = tester.renderObject(find.byKey(key2));
      final RenderBox rowBox = tester.renderObject(find.byType(Row));

      // The two Texts are baseline aligned, so some portion of them extends
      // both above and below the baseline. The first has a huge font size, so
      // it extends higher above the baseline than usual. The second has many
      // lines, but being aligned by the first line's baseline, they hang far
      // below the baseline. The size of the parent row is just enough to
      // contain both of them.
      const double ahemBaselineLocation = 0.8; // https://web-platform-tests.org/writing-tests/ahem.html
      const double aboveBaseline1 = fontSize1 * ahemBaselineLocation;
      const double belowBaseline1 = fontSize1 * (1 - ahemBaselineLocation);
      const double aboveBaseline2 = fontSize2 * ahemBaselineLocation;
      const double belowBaseline2 = fontSize2 * (1 - ahemBaselineLocation) + fontSize2 * 6;
      final double aboveBaseline = math.max(aboveBaseline1, aboveBaseline2);
      final double belowBaseline = math.max(belowBaseline1, belowBaseline2);
      expect(rowBox.size.height, greaterThan(textBox1.size.height));
      expect(rowBox.size.height, greaterThan(textBox2.size.height));
      expect(rowBox.size.height, closeTo(aboveBaseline + belowBaseline, .001));
      expect(tester.getTopLeft(find.byKey(key1)).dy, 0);
      expect(
        tester.getTopLeft(find.byKey(key2)).dy,
        closeTo(aboveBaseline1 - aboveBaseline2, .001),
      );
205
    }, skip: isBrowser);
206 207
  });

208 209 210 211 212 213 214 215 216 217
  test('UnconstrainedBox toString', () {
    expect(
      const UnconstrainedBox(constrainedAxis: Axis.vertical,).toString(),
      equals('UnconstrainedBox(alignment: center, constrainedAxis: vertical)'),
    );
    expect(
      const UnconstrainedBox(constrainedAxis: Axis.horizontal, textDirection: TextDirection.rtl, alignment: Alignment.topRight).toString(),
      equals('UnconstrainedBox(alignment: topRight, constrainedAxis: horizontal, textDirection: rtl)'),
    );
  });
218 219
}

220
HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox);
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

class HitsRenderBox extends Matcher {
  const HitsRenderBox(this.renderBox);

  final RenderBox renderBox;

  @override
  Description describe(Description description) =>
    description.add('hit test result contains ').addDescriptionOf(renderBox);

  @override
  bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
    final HitTestResult hitTestResult = item;
    return hitTestResult.path.where(
      (HitTestEntry entry) => entry.target == renderBox
    ).isNotEmpty;
  }
}

240
DoesNotHitRenderBox doesNotHit(RenderBox renderBox) => DoesNotHitRenderBox(renderBox);
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

class DoesNotHitRenderBox extends Matcher {
  const DoesNotHitRenderBox(this.renderBox);

  final RenderBox renderBox;

  @override
  Description describe(Description description) =>
    description.add('hit test result doesn\'t contain ').addDescriptionOf(renderBox);

  @override
  bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
    final HitTestResult hitTestResult = item;
    return hitTestResult.path.where(
      (HitTestEntry entry) => entry.target == renderBox
    ).isEmpty;
  }
}