• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6import 'package:flutter_test/flutter_test.dart';
7import 'package:flutter/material.dart';
8import 'package:flutter/widgets.dart';
9import 'package:flutter/rendering.dart';
10
11void main() {
12  group('PhysicalShape', () {
13    testWidgets('properties', (WidgetTester tester) async {
14      await tester.pumpWidget(
15        const PhysicalShape(
16          clipper: ShapeBorderClipper(shape: CircleBorder()),
17          elevation: 2.0,
18          color: Color(0xFF0000FF),
19          shadowColor: Color(0xFF00FF00),
20        )
21      );
22      final RenderPhysicalShape renderObject = tester.renderObject(find.byType(PhysicalShape));
23      expect(renderObject.clipper, const ShapeBorderClipper(shape: CircleBorder()));
24      expect(renderObject.color, const Color(0xFF0000FF));
25      expect(renderObject.shadowColor, const Color(0xFF00FF00));
26      expect(renderObject.elevation, 2.0);
27    });
28
29    testWidgets('hit test', (WidgetTester tester) async {
30      await tester.pumpWidget(
31        PhysicalShape(
32          clipper: const ShapeBorderClipper(shape: CircleBorder()),
33          elevation: 2.0,
34          color: const Color(0xFF0000FF),
35          shadowColor: const Color(0xFF00FF00),
36          child: Container(color: const Color(0xFF0000FF)),
37        )
38      );
39
40      final RenderPhysicalShape renderPhysicalShape =
41        tester.renderObject(find.byType(PhysicalShape));
42
43      // The viewport is 800x600, the CircleBorder is centered and fits
44      // the shortest edge, so we get a circle of radius 300, centered at
45      // (400, 300).
46      //
47      // We test by sampling a few points around the left-most point of the
48      // circle (100, 300).
49
50      expect(tester.hitTestOnBinding(const Offset(99.0, 300.0)), doesNotHit(renderPhysicalShape));
51      expect(tester.hitTestOnBinding(const Offset(100.0, 300.0)), hits(renderPhysicalShape));
52      expect(tester.hitTestOnBinding(const Offset(100.0, 299.0)), doesNotHit(renderPhysicalShape));
53      expect(tester.hitTestOnBinding(const Offset(100.0, 301.0)), doesNotHit(renderPhysicalShape));
54    }, skip: isBrowser);
55
56  });
57
58  group('FractionalTranslation', () {
59    testWidgets('hit test - entirely inside the bounding box', (WidgetTester tester) async {
60      final GlobalKey key1 = GlobalKey();
61      bool _pointerDown = false;
62
63      await tester.pumpWidget(
64        Center(
65          child: FractionalTranslation(
66            translation: Offset.zero,
67            transformHitTests: true,
68            child: Listener(
69              onPointerDown: (PointerDownEvent event) {
70                _pointerDown = true;
71              },
72              child: SizedBox(
73                key: key1,
74                width: 100.0,
75                height: 100.0,
76                child: Container(
77                  color: const Color(0xFF0000FF)
78                ),
79              ),
80            ),
81          ),
82        )
83      );
84      expect(_pointerDown, isFalse);
85      await tester.tap(find.byKey(key1));
86      expect(_pointerDown, isTrue);
87    });
88
89    testWidgets('hit test - partially inside the bounding box', (WidgetTester tester) async {
90      final GlobalKey key1 = GlobalKey();
91      bool _pointerDown = false;
92
93      await tester.pumpWidget(
94        Center(
95          child: FractionalTranslation(
96            translation: const Offset(0.5, 0.5),
97            transformHitTests: true,
98            child: Listener(
99              onPointerDown: (PointerDownEvent event) {
100                _pointerDown = true;
101              },
102              child: SizedBox(
103                key: key1,
104                width: 100.0,
105                height: 100.0,
106                child: Container(
107                  color: const Color(0xFF0000FF)
108                ),
109              ),
110            ),
111          ),
112        )
113      );
114      expect(_pointerDown, isFalse);
115      await tester.tap(find.byKey(key1));
116      expect(_pointerDown, isTrue);
117    });
118
119    testWidgets('hit test - completely outside the bounding box', (WidgetTester tester) async {
120      final GlobalKey key1 = GlobalKey();
121      bool _pointerDown = false;
122
123      await tester.pumpWidget(
124        Center(
125          child: FractionalTranslation(
126            translation: const Offset(1.0, 1.0),
127            transformHitTests: true,
128            child: Listener(
129              onPointerDown: (PointerDownEvent event) {
130                _pointerDown = true;
131              },
132              child: SizedBox(
133                key: key1,
134                width: 100.0,
135                height: 100.0,
136                child: Container(
137                  color: const Color(0xFF0000FF)
138                ),
139              ),
140            ),
141          ),
142        )
143      );
144      expect(_pointerDown, isFalse);
145      await tester.tap(find.byKey(key1));
146      expect(_pointerDown, isTrue);
147    });
148  });
149
150  group('Row', () {
151    testWidgets('multiple baseline aligned children', (WidgetTester tester) async {
152      final UniqueKey key1 = UniqueKey();
153      final UniqueKey key2 = UniqueKey();
154      const double fontSize1 = 54;
155      const double fontSize2 = 14;
156
157      await tester.pumpWidget(
158        MaterialApp(
159          home: Scaffold(
160            body: Container(
161              child: Row(
162                crossAxisAlignment: CrossAxisAlignment.baseline,
163                textBaseline: TextBaseline.alphabetic,
164                children: <Widget>[
165                  Text('big text',
166                    key: key1,
167                    style: const TextStyle(fontSize: fontSize1),
168                  ),
169                  Text('one\ntwo\nthree\nfour\nfive\nsix\nseven',
170                    key: key2,
171                    style: const TextStyle(fontSize: fontSize2)
172                  ),
173                ],
174              ),
175            ),
176          ),
177        ),
178      );
179
180      final RenderBox textBox1 = tester.renderObject(find.byKey(key1));
181      final RenderBox textBox2 = tester.renderObject(find.byKey(key2));
182      final RenderBox rowBox = tester.renderObject(find.byType(Row));
183
184      // The two Texts are baseline aligned, so some portion of them extends
185      // both above and below the baseline. The first has a huge font size, so
186      // it extends higher above the baseline than usual. The second has many
187      // lines, but being aligned by the first line's baseline, they hang far
188      // below the baseline. The size of the parent row is just enough to
189      // contain both of them.
190      const double ahemBaselineLocation = 0.8; // https://web-platform-tests.org/writing-tests/ahem.html
191      const double aboveBaseline1 = fontSize1 * ahemBaselineLocation;
192      const double belowBaseline1 = fontSize1 * (1 - ahemBaselineLocation);
193      const double aboveBaseline2 = fontSize2 * ahemBaselineLocation;
194      const double belowBaseline2 = fontSize2 * (1 - ahemBaselineLocation) + fontSize2 * 6;
195      final double aboveBaseline = math.max(aboveBaseline1, aboveBaseline2);
196      final double belowBaseline = math.max(belowBaseline1, belowBaseline2);
197      expect(rowBox.size.height, greaterThan(textBox1.size.height));
198      expect(rowBox.size.height, greaterThan(textBox2.size.height));
199      expect(rowBox.size.height, closeTo(aboveBaseline + belowBaseline, .001));
200      expect(tester.getTopLeft(find.byKey(key1)).dy, 0);
201      expect(
202        tester.getTopLeft(find.byKey(key2)).dy,
203        closeTo(aboveBaseline1 - aboveBaseline2, .001),
204      );
205    }, skip: isBrowser);
206  });
207
208  test('UnconstrainedBox toString', () {
209    expect(
210      const UnconstrainedBox(constrainedAxis: Axis.vertical,).toString(),
211      equals('UnconstrainedBox(alignment: center, constrainedAxis: vertical)'),
212    );
213    expect(
214      const UnconstrainedBox(constrainedAxis: Axis.horizontal, textDirection: TextDirection.rtl, alignment: Alignment.topRight).toString(),
215      equals('UnconstrainedBox(alignment: topRight, constrainedAxis: horizontal, textDirection: rtl)'),
216    );
217  });
218}
219
220HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox);
221
222class HitsRenderBox extends Matcher {
223  const HitsRenderBox(this.renderBox);
224
225  final RenderBox renderBox;
226
227  @override
228  Description describe(Description description) =>
229    description.add('hit test result contains ').addDescriptionOf(renderBox);
230
231  @override
232  bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
233    final HitTestResult hitTestResult = item;
234    return hitTestResult.path.where(
235      (HitTestEntry entry) => entry.target == renderBox
236    ).isNotEmpty;
237  }
238}
239
240DoesNotHitRenderBox doesNotHit(RenderBox renderBox) => DoesNotHitRenderBox(renderBox);
241
242class DoesNotHitRenderBox extends Matcher {
243  const DoesNotHitRenderBox(this.renderBox);
244
245  final RenderBox renderBox;
246
247  @override
248  Description describe(Description description) =>
249    description.add('hit test result doesn\'t contain ').addDescriptionOf(renderBox);
250
251  @override
252  bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
253    final HitTestResult hitTestResult = item;
254    return hitTestResult.path.where(
255      (HitTestEntry entry) => entry.target == renderBox
256    ).isEmpty;
257  }
258}
259