1 | // Copyright 2014 The Flutter 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 | |
5 | // This file is run as part of a reduced test set in CI on Mac and Windows |
6 | // machines. |
7 | @Tags(<String>['reduced-test-set' ]) |
8 | library; |
9 | |
10 | import 'dart:math' as math; |
11 | import 'dart:ui' as ui; |
12 | import 'dart:ui'; |
13 | |
14 | import 'package:flutter/gestures.dart'; |
15 | import 'package:flutter/material.dart'; |
16 | import 'package:flutter/rendering.dart'; |
17 | import 'package:flutter_test/flutter_test.dart'; |
18 | |
19 | import 'semantics_tester.dart'; |
20 | |
21 | void main() { |
22 | group('RawImage' , () { |
23 | testWidgets('properties' , (WidgetTester tester) async { |
24 | final ui.Image image1 = (await tester.runAsync<ui.Image>(() => createTestImage()))!; |
25 | addTearDown(image1.dispose); |
26 | |
27 | await tester.pumpWidget( |
28 | Directionality( |
29 | textDirection: TextDirection.ltr, |
30 | child: RawImage(image: image1), |
31 | ), |
32 | ); |
33 | final RenderImage renderObject = tester.firstRenderObject<RenderImage>(find.byType(RawImage)); |
34 | |
35 | // Expect default values |
36 | expect(renderObject.image!.isCloneOf(image1), true); |
37 | expect(renderObject.debugImageLabel, null); |
38 | expect(renderObject.width, null); |
39 | expect(renderObject.height, null); |
40 | expect(renderObject.scale, 1.0); |
41 | expect(renderObject.color, null); |
42 | expect(renderObject.opacity, null); |
43 | expect(renderObject.colorBlendMode, null); |
44 | expect(renderObject.fit, null); |
45 | expect(renderObject.alignment, Alignment.center); |
46 | expect(renderObject.repeat, ImageRepeat.noRepeat); |
47 | expect(renderObject.centerSlice, null); |
48 | expect(renderObject.matchTextDirection, false); |
49 | expect(renderObject.invertColors, false); |
50 | expect(renderObject.filterQuality, FilterQuality.medium); |
51 | expect(renderObject.isAntiAlias, false); |
52 | |
53 | final ui.Image image2 = (await tester.runAsync<ui.Image>(() => createTestImage(width: 2, height: 2)))!; |
54 | addTearDown(image2.dispose); |
55 | const String debugImageLabel = 'debugImageLabel' ; |
56 | const double width = 1; |
57 | const double height = 1; |
58 | const double scale = 2.0; |
59 | const Color color = Colors.black; |
60 | const Animation<double> opacity = AlwaysStoppedAnimation<double>(0.0); |
61 | const BlendMode colorBlendMode = BlendMode.difference; |
62 | const BoxFit fit = BoxFit.contain; |
63 | const AlignmentGeometry alignment = Alignment.topCenter; |
64 | const ImageRepeat repeat = ImageRepeat.repeat; |
65 | const Rect centerSlice = Rect.fromLTWH(0, 0, width, height); |
66 | const bool matchTextDirection = true; |
67 | const bool invertColors = true; |
68 | const FilterQuality filterQuality = FilterQuality.high; |
69 | const bool isAntiAlias = true; |
70 | |
71 | await tester.pumpWidget( |
72 | Directionality( |
73 | textDirection: TextDirection.ltr, |
74 | child: RawImage( |
75 | image: image2, |
76 | debugImageLabel: debugImageLabel, |
77 | width: width, |
78 | height: height, |
79 | scale: scale, |
80 | color: color, |
81 | opacity: opacity, |
82 | colorBlendMode: colorBlendMode, |
83 | fit: fit, |
84 | alignment: alignment, |
85 | repeat: repeat, |
86 | centerSlice: centerSlice, |
87 | matchTextDirection: matchTextDirection, |
88 | invertColors: invertColors, |
89 | filterQuality: filterQuality, |
90 | isAntiAlias: isAntiAlias, |
91 | ), |
92 | ), |
93 | ); |
94 | |
95 | expect(renderObject.image!.isCloneOf(image2), true); |
96 | expect(renderObject.debugImageLabel, debugImageLabel); |
97 | expect(renderObject.width, width); |
98 | expect(renderObject.height, height); |
99 | expect(renderObject.scale, scale); |
100 | expect(renderObject.color, color); |
101 | expect(renderObject.opacity, opacity); |
102 | expect(renderObject.colorBlendMode, colorBlendMode); |
103 | expect(renderObject.fit, fit); |
104 | expect(renderObject.alignment, alignment); |
105 | expect(renderObject.repeat, repeat); |
106 | expect(renderObject.centerSlice, centerSlice); |
107 | expect(renderObject.matchTextDirection, matchTextDirection); |
108 | expect(renderObject.invertColors, invertColors); |
109 | expect(renderObject.filterQuality, filterQuality); |
110 | expect(renderObject.isAntiAlias, isAntiAlias); |
111 | }); |
112 | }); |
113 | |
114 | group('PhysicalShape' , () { |
115 | testWidgets('properties' , (WidgetTester tester) async { |
116 | await tester.pumpWidget( |
117 | const PhysicalShape( |
118 | clipper: ShapeBorderClipper(shape: CircleBorder()), |
119 | elevation: 2.0, |
120 | color: Color(0xFF0000FF), |
121 | shadowColor: Color(0xFF00FF00), |
122 | ), |
123 | ); |
124 | final RenderPhysicalShape renderObject = tester.renderObject(find.byType(PhysicalShape)); |
125 | expect(renderObject.clipper, const ShapeBorderClipper(shape: CircleBorder())); |
126 | expect(renderObject.color, const Color(0xFF0000FF)); |
127 | expect(renderObject.shadowColor, const Color(0xFF00FF00)); |
128 | expect(renderObject.elevation, 2.0); |
129 | }); |
130 | |
131 | testWidgets('hit test' , (WidgetTester tester) async { |
132 | await tester.pumpWidget( |
133 | PhysicalShape( |
134 | clipper: const ShapeBorderClipper(shape: CircleBorder()), |
135 | elevation: 2.0, |
136 | color: const Color(0xFF0000FF), |
137 | shadowColor: const Color(0xFF00FF00), |
138 | child: Container(color: const Color(0xFF0000FF)), |
139 | ), |
140 | ); |
141 | |
142 | final RenderPhysicalShape renderPhysicalShape = |
143 | tester.renderObject(find.byType(PhysicalShape)); |
144 | |
145 | // The viewport is 800x600, the CircleBorder is centered and fits |
146 | // the shortest edge, so we get a circle of radius 300, centered at |
147 | // (400, 300). |
148 | // |
149 | // We test by sampling a few points around the left-most point of the |
150 | // circle (100, 300). |
151 | |
152 | expect(tester.hitTestOnBinding(const Offset(99.0, 300.0)), doesNotHit(renderPhysicalShape)); |
153 | expect(tester.hitTestOnBinding(const Offset(100.0, 300.0)), hits(renderPhysicalShape)); |
154 | expect(tester.hitTestOnBinding(const Offset(100.0, 299.0)), doesNotHit(renderPhysicalShape)); |
155 | expect(tester.hitTestOnBinding(const Offset(100.0, 301.0)), doesNotHit(renderPhysicalShape)); |
156 | }); |
157 | |
158 | }); |
159 | |
160 | group('FractionalTranslation' , () { |
161 | testWidgets('hit test - entirely inside the bounding box' , (WidgetTester tester) async { |
162 | final GlobalKey key1 = GlobalKey(); |
163 | bool pointerDown = false; |
164 | |
165 | await tester.pumpWidget( |
166 | Center( |
167 | child: FractionalTranslation( |
168 | translation: Offset.zero, |
169 | child: Listener( |
170 | onPointerDown: (PointerDownEvent event) { |
171 | pointerDown = true; |
172 | }, |
173 | child: SizedBox( |
174 | key: key1, |
175 | width: 100.0, |
176 | height: 100.0, |
177 | child: Container( |
178 | color: const Color(0xFF0000FF), |
179 | ), |
180 | ), |
181 | ), |
182 | ), |
183 | ), |
184 | ); |
185 | expect(pointerDown, isFalse); |
186 | await tester.tap(find.byKey(key1)); |
187 | expect(pointerDown, isTrue); |
188 | }); |
189 | |
190 | testWidgets('hit test - partially inside the bounding box' , (WidgetTester tester) async { |
191 | final GlobalKey key1 = GlobalKey(); |
192 | bool pointerDown = false; |
193 | |
194 | await tester.pumpWidget( |
195 | Center( |
196 | child: FractionalTranslation( |
197 | translation: const Offset(0.5, 0.5), |
198 | child: Listener( |
199 | onPointerDown: (PointerDownEvent event) { |
200 | pointerDown = true; |
201 | }, |
202 | child: SizedBox( |
203 | key: key1, |
204 | width: 100.0, |
205 | height: 100.0, |
206 | child: Container( |
207 | color: const Color(0xFF0000FF), |
208 | ), |
209 | ), |
210 | ), |
211 | ), |
212 | ), |
213 | ); |
214 | expect(pointerDown, isFalse); |
215 | await tester.tap(find.byKey(key1)); |
216 | expect(pointerDown, isTrue); |
217 | }); |
218 | |
219 | testWidgets('hit test - completely outside the bounding box' , (WidgetTester tester) async { |
220 | final GlobalKey key1 = GlobalKey(); |
221 | bool pointerDown = false; |
222 | |
223 | await tester.pumpWidget( |
224 | Center( |
225 | child: FractionalTranslation( |
226 | translation: const Offset(1.0, 1.0), |
227 | child: Listener( |
228 | onPointerDown: (PointerDownEvent event) { |
229 | pointerDown = true; |
230 | }, |
231 | child: SizedBox( |
232 | key: key1, |
233 | width: 100.0, |
234 | height: 100.0, |
235 | child: Container( |
236 | color: const Color(0xFF0000FF), |
237 | ), |
238 | ), |
239 | ), |
240 | ), |
241 | ), |
242 | ); |
243 | expect(pointerDown, isFalse); |
244 | await tester.tap(find.byKey(key1)); |
245 | expect(pointerDown, isTrue); |
246 | }); |
247 | |
248 | testWidgets('semantics bounds are updated' , (WidgetTester tester) async { |
249 | final GlobalKey fractionalTranslationKey = GlobalKey(); |
250 | final GlobalKey textKey = GlobalKey(); |
251 | Offset offset = const Offset(0.4, 0.4); |
252 | |
253 | await tester.pumpWidget( |
254 | StatefulBuilder( |
255 | builder: (BuildContext context, StateSetter setState) { |
256 | return Directionality( |
257 | textDirection: TextDirection.ltr, |
258 | child: Center( |
259 | child: Semantics( |
260 | explicitChildNodes: true, |
261 | child: FractionalTranslation( |
262 | key: fractionalTranslationKey, |
263 | translation: offset, |
264 | child: GestureDetector( |
265 | onTap: () { |
266 | setState(() { |
267 | offset = const Offset(0.8, 0.8); |
268 | }); |
269 | }, |
270 | child: SizedBox( |
271 | width: 100.0, |
272 | height: 100.0, |
273 | child: Text( |
274 | 'foo' , |
275 | key: textKey, |
276 | ), |
277 | ), |
278 | ), |
279 | ), |
280 | ), |
281 | ), |
282 | ); |
283 | }, |
284 | ), |
285 | ); |
286 | |
287 | expect( |
288 | tester.getSemantics(find.byKey(textKey)).transform, |
289 | Matrix4( |
290 | 3.0, 0.0, 0.0, 0.0, |
291 | 0.0, 3.0, 0.0, 0.0, |
292 | 0.0, 0.0, 1.0, 0.0, |
293 | 1170.0, 870.0, 0.0, 1.0, |
294 | ), |
295 | ); |
296 | |
297 | await tester.tap(find.byKey(fractionalTranslationKey), warnIfMissed: false); // RenderFractionalTranslation can't be hit |
298 | await tester.pump(); |
299 | expect( |
300 | tester.getSemantics(find.byKey(textKey)).transform, |
301 | Matrix4( |
302 | 3.0, 0.0, 0.0, 0.0, |
303 | 0.0, 3.0, 0.0, 0.0, |
304 | 0.0, 0.0, 1.0, 0.0, |
305 | 1290.0, 990.0, 0.0, 1.0, |
306 | ), |
307 | ); |
308 | }); |
309 | }); |
310 | |
311 | group('Semantics' , () { |
312 | testWidgets('Semantics can set attributed Text' , (WidgetTester tester) async { |
313 | final UniqueKey key = UniqueKey(); |
314 | await tester.pumpWidget( |
315 | MaterialApp( |
316 | home: Scaffold( |
317 | body: Semantics( |
318 | key: key, |
319 | attributedLabel: AttributedString( |
320 | 'label' , |
321 | attributes: <StringAttribute>[ |
322 | SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), |
323 | ], |
324 | ), |
325 | attributedValue: AttributedString( |
326 | 'value' , |
327 | attributes: <StringAttribute>[ |
328 | LocaleStringAttribute(range: const TextRange(start: 0, end: 5), locale: const Locale('en' , 'MX' )), |
329 | ], |
330 | ), |
331 | attributedHint: AttributedString( |
332 | 'hint' , |
333 | attributes: <StringAttribute>[ |
334 | SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)), |
335 | ], |
336 | ), |
337 | child: const Placeholder(), |
338 | ) |
339 | ), |
340 | ) |
341 | ); |
342 | final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel; |
343 | expect(attributedLabel.string, 'label' ); |
344 | expect(attributedLabel.attributes.length, 1); |
345 | expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue); |
346 | expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5)); |
347 | |
348 | final AttributedString attributedValue = tester.getSemantics(find.byKey(key)).attributedValue; |
349 | expect(attributedValue.string, 'value' ); |
350 | expect(attributedValue.attributes.length, 1); |
351 | expect(attributedValue.attributes[0] is LocaleStringAttribute, isTrue); |
352 | final LocaleStringAttribute valueLocale = attributedValue.attributes[0] as LocaleStringAttribute; |
353 | expect(valueLocale.range, const TextRange(start:0, end: 5)); |
354 | expect(valueLocale.locale, const Locale('en' , 'MX' )); |
355 | |
356 | final AttributedString attributedHint = tester.getSemantics(find.byKey(key)).attributedHint; |
357 | expect(attributedHint.string, 'hint' ); |
358 | expect(attributedHint.attributes.length, 1); |
359 | expect(attributedHint.attributes[0] is SpellOutStringAttribute, isTrue); |
360 | expect(attributedHint.attributes[0].range, const TextRange(start:1, end: 2)); |
361 | }); |
362 | |
363 | testWidgets('Semantics can merge attributed strings' , (WidgetTester tester) async { |
364 | final UniqueKey key = UniqueKey(); |
365 | await tester.pumpWidget( |
366 | MaterialApp( |
367 | home: Scaffold( |
368 | body: Semantics( |
369 | key: key, |
370 | attributedLabel: AttributedString( |
371 | 'label' , |
372 | attributes: <StringAttribute>[ |
373 | SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), |
374 | ], |
375 | ), |
376 | attributedHint: AttributedString( |
377 | 'hint' , |
378 | attributes: <StringAttribute>[ |
379 | SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)), |
380 | ], |
381 | ), |
382 | child: Semantics( |
383 | attributedLabel: AttributedString( |
384 | 'label' , |
385 | attributes: <StringAttribute>[ |
386 | SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), |
387 | ], |
388 | ), |
389 | attributedHint: AttributedString( |
390 | 'hint' , |
391 | attributes: <StringAttribute>[ |
392 | SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)), |
393 | ], |
394 | ), |
395 | child: const Placeholder(), |
396 | ) |
397 | ) |
398 | ), |
399 | ) |
400 | ); |
401 | final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel; |
402 | expect(attributedLabel.string, 'label\nlabel' ); |
403 | expect(attributedLabel.attributes.length, 2); |
404 | expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue); |
405 | expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5)); |
406 | expect(attributedLabel.attributes[1] is SpellOutStringAttribute, isTrue); |
407 | expect(attributedLabel.attributes[1].range, const TextRange(start:6, end: 11)); |
408 | |
409 | final AttributedString attributedHint = tester.getSemantics(find.byKey(key)).attributedHint; |
410 | expect(attributedHint.string, 'hint\nhint' ); |
411 | expect(attributedHint.attributes.length, 2); |
412 | expect(attributedHint.attributes[0] is SpellOutStringAttribute, isTrue); |
413 | expect(attributedHint.attributes[0].range, const TextRange(start:1, end: 2)); |
414 | expect(attributedHint.attributes[1] is SpellOutStringAttribute, isTrue); |
415 | expect(attributedHint.attributes[1].range, const TextRange(start:6, end: 7)); |
416 | }); |
417 | |
418 | testWidgets('Semantics can merge attributed strings with non attributed string' , (WidgetTester tester) async { |
419 | final UniqueKey key = UniqueKey(); |
420 | await tester.pumpWidget( |
421 | MaterialApp( |
422 | home: Scaffold( |
423 | body: Semantics( |
424 | key: key, |
425 | attributedLabel: AttributedString( |
426 | 'label1' , |
427 | attributes: <StringAttribute>[ |
428 | SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), |
429 | ], |
430 | ), |
431 | child: Semantics( |
432 | label: 'label2' , |
433 | child: Semantics( |
434 | attributedLabel: AttributedString( |
435 | 'label3' , |
436 | attributes: <StringAttribute>[ |
437 | SpellOutStringAttribute(range: const TextRange(start: 1, end: 3)), |
438 | ], |
439 | ), |
440 | child: const Placeholder(), |
441 | ), |
442 | ) |
443 | ) |
444 | ), |
445 | ) |
446 | ); |
447 | final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel; |
448 | expect(attributedLabel.string, 'label1\nlabel2\nlabel3' ); |
449 | expect(attributedLabel.attributes.length, 2); |
450 | expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue); |
451 | expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5)); |
452 | expect(attributedLabel.attributes[1] is SpellOutStringAttribute, isTrue); |
453 | expect(attributedLabel.attributes[1].range, const TextRange(start:15, end: 17)); |
454 | }); |
455 | }); |
456 | |
457 | group('Row' , () { |
458 | testWidgets('multiple baseline aligned children' , (WidgetTester tester) async { |
459 | final UniqueKey key1 = UniqueKey(); |
460 | final UniqueKey key2 = UniqueKey(); |
461 | // The point size of the font must be a multiple of 4 until |
462 | // https://github.com/flutter/flutter/issues/122066 is resolved. |
463 | const double fontSize1 = 52; |
464 | const double fontSize2 = 12; |
465 | |
466 | await tester.pumpWidget( |
467 | MaterialApp( |
468 | theme: ThemeData(useMaterial3: false), |
469 | home: Scaffold( |
470 | body: Row( |
471 | crossAxisAlignment: CrossAxisAlignment.baseline, |
472 | textBaseline: TextBaseline.alphabetic, |
473 | children: <Widget>[ |
474 | Text('big text' , |
475 | key: key1, |
476 | style: const TextStyle(fontFamily: 'FlutterTest' , fontSize: fontSize1, height: 1.0), |
477 | ), |
478 | Text('one\ntwo\nthree\nfour\nfive\nsix\nseven' , |
479 | key: key2, |
480 | style: const TextStyle(fontFamily: 'FlutterTest' , fontSize: fontSize2, height: 1.0), |
481 | ), |
482 | ], |
483 | ), |
484 | ), |
485 | ), |
486 | ); |
487 | |
488 | final RenderBox textBox1 = tester.renderObject(find.byKey(key1)); |
489 | final RenderBox textBox2 = tester.renderObject(find.byKey(key2)); |
490 | final RenderBox rowBox = tester.renderObject(find.byType(Row)); |
491 | |
492 | // The two Texts are baseline aligned, so some portion of them extends |
493 | // both above and below the baseline. The first has a huge font size, so |
494 | // it extends higher above the baseline than usual. The second has many |
495 | // lines, but being aligned by the first line's baseline, they hang far |
496 | // below the baseline. The size of the parent row is just enough to |
497 | // contain both of them. |
498 | const double ascentRatio = 0.75; |
499 | const double aboveBaseline1 = fontSize1 * ascentRatio; |
500 | const double belowBaseline1 = fontSize1 * (1 - ascentRatio); |
501 | const double aboveBaseline2 = fontSize2 * ascentRatio; |
502 | const double belowBaseline2 = fontSize2 * (1 - ascentRatio) + fontSize2 * 6; |
503 | final double aboveBaseline = math.max(aboveBaseline1, aboveBaseline2); |
504 | final double belowBaseline = math.max(belowBaseline1, belowBaseline2); |
505 | expect(rowBox.size.height, greaterThan(textBox1.size.height)); |
506 | expect(rowBox.size.height, greaterThan(textBox2.size.height)); |
507 | expect(rowBox.size.height, aboveBaseline + belowBaseline, ); |
508 | expect(tester.getTopLeft(find.byKey(key1)).dy, 0); |
509 | expect(tester.getTopLeft(find.byKey(key2)).dy, aboveBaseline1 - aboveBaseline2); |
510 | }); |
511 | |
512 | testWidgets('baseline aligned children account for a larger, no-baseline child size' , (WidgetTester tester) async { |
513 | // Regression test for https://github.com/flutter/flutter/issues/58898 |
514 | final UniqueKey key1 = UniqueKey(); |
515 | final UniqueKey key2 = UniqueKey(); |
516 | // The point size of the font must be a multiple of 4 until |
517 | // https://github.com/flutter/flutter/issues/122066 is resolved. |
518 | const double fontSize1 = 52; |
519 | const double fontSize2 = 12; |
520 | |
521 | await tester.pumpWidget( |
522 | MaterialApp( |
523 | theme: ThemeData(useMaterial3: false), |
524 | home: Scaffold( |
525 | body: Row( |
526 | crossAxisAlignment: CrossAxisAlignment.baseline, |
527 | textBaseline: TextBaseline.alphabetic, |
528 | children: <Widget>[ |
529 | Text('big text' , |
530 | key: key1, |
531 | style: const TextStyle(fontFamily: 'FlutterTest' , fontSize: fontSize1, height: 1.0), |
532 | ), |
533 | Text('one\ntwo\nthree\nfour\nfive\nsix\nseven' , |
534 | key: key2, |
535 | style: const TextStyle(fontFamily: 'FlutterTest' , fontSize: fontSize2, height: 1.0), |
536 | ), |
537 | const FlutterLogo(size: 250), |
538 | ], |
539 | ), |
540 | ), |
541 | ), |
542 | ); |
543 | |
544 | final RenderBox textBox1 = tester.renderObject(find.byKey(key1)); |
545 | final RenderBox textBox2 = tester.renderObject(find.byKey(key2)); |
546 | final RenderBox rowBox = tester.renderObject(find.byType(Row)); |
547 | |
548 | // The two Texts are baseline aligned, so some portion of them extends |
549 | // both above and below the baseline. The first has a huge font size, so |
550 | // it extends higher above the baseline than usual. The second has many |
551 | // lines, but being aligned by the first line's baseline, they hang far |
552 | // below the baseline. The FlutterLogo extends further than both Texts, |
553 | // so the size of the parent row should contain the FlutterLogo as well. |
554 | const double ascentRatio = 0.75; |
555 | const double aboveBaseline1 = fontSize1 * ascentRatio; |
556 | const double aboveBaseline2 = fontSize2 * ascentRatio; |
557 | expect(rowBox.size.height, greaterThan(textBox1.size.height)); |
558 | expect(rowBox.size.height, greaterThan(textBox2.size.height)); |
559 | expect(rowBox.size.height, 250); |
560 | expect(tester.getTopLeft(find.byKey(key1)).dy, 0); |
561 | expect( |
562 | tester.getTopLeft(find.byKey(key2)).dy, |
563 | aboveBaseline1 - aboveBaseline2, |
564 | ); |
565 | }); |
566 | }); |
567 | |
568 | test('UnconstrainedBox toString' , () { |
569 | expect( |
570 | const UnconstrainedBox(constrainedAxis: Axis.vertical).toString(), |
571 | equals('UnconstrainedBox(alignment: Alignment.center, constrainedAxis: vertical)' ), |
572 | ); |
573 | |
574 | expect( |
575 | const UnconstrainedBox(constrainedAxis: Axis.horizontal, textDirection: TextDirection.rtl, alignment: Alignment.topRight).toString(), |
576 | equals('UnconstrainedBox(alignment: Alignment.topRight, constrainedAxis: horizontal, textDirection: rtl)' ), |
577 | ); |
578 | }); |
579 | |
580 | testWidgets('UnconstrainedBox can set and update clipBehavior' , (WidgetTester tester) async { |
581 | await tester.pumpWidget(const UnconstrainedBox()); |
582 | final RenderConstraintsTransformBox renderObject = tester.allRenderObjects.whereType<RenderConstraintsTransformBox>().first; |
583 | expect(renderObject.clipBehavior, equals(Clip.none)); |
584 | |
585 | await tester.pumpWidget(const UnconstrainedBox(clipBehavior: Clip.antiAlias)); |
586 | expect(renderObject.clipBehavior, equals(Clip.antiAlias)); |
587 | }); |
588 | |
589 | testWidgets('UnconstrainedBox warns only when clipBehavior is Clip.none' , (WidgetTester tester) async { |
590 | for (final Clip? clip in <Clip?>[null, ...Clip.values]) { |
591 | // Clear any render objects that were there before so that we can see more |
592 | // than one error. Otherwise, it just throws the first one and skips the |
593 | // rest, since the render objects haven't changed. |
594 | await tester.pumpWidget(const SizedBox()); |
595 | await tester.pumpWidget( |
596 | Center( |
597 | child: ConstrainedBox( |
598 | constraints: const BoxConstraints(maxHeight: 200, maxWidth: 200), |
599 | child: clip == null |
600 | ? const UnconstrainedBox(child: SizedBox(width: 400, height: 400)) |
601 | : UnconstrainedBox( |
602 | clipBehavior: clip, |
603 | child: const SizedBox(width: 400, height: 400), |
604 | ), |
605 | ), |
606 | ), |
607 | ); |
608 | |
609 | final RenderConstraintsTransformBox renderObject = tester.allRenderObjects.whereType<RenderConstraintsTransformBox>().first; |
610 | |
611 | // Defaults to Clip.none |
612 | expect(renderObject.clipBehavior, equals(clip ?? Clip.none), reason: 'for clip = $clip' ); |
613 | |
614 | switch (clip) { |
615 | case null: |
616 | case Clip.none: |
617 | // the UnconstrainedBox overflows. |
618 | final dynamic exception = tester.takeException(); |
619 | expect(exception, isFlutterError, reason: 'for clip = $clip' ); |
620 | // ignore: avoid_dynamic_calls |
621 | expect(exception.diagnostics.first.level, DiagnosticLevel.summary, reason: 'for clip = $clip' ); |
622 | expect( |
623 | // ignore: avoid_dynamic_calls |
624 | exception.diagnostics.first.toString(), |
625 | startsWith('A RenderConstraintsTransformBox overflowed' ), |
626 | reason: 'for clip = $clip' , |
627 | ); |
628 | case Clip.hardEdge: |
629 | case Clip.antiAlias: |
630 | case Clip.antiAliasWithSaveLayer: |
631 | expect(tester.takeException(), isNull, reason: 'for clip = $clip' ); |
632 | } |
633 | } |
634 | }); |
635 | |
636 | group('ConstraintsTransformBox' , () { |
637 | test('toString' , () { |
638 | expect( |
639 | const ConstraintsTransformBox( |
640 | constraintsTransform: ConstraintsTransformBox.unconstrained, |
641 | ).toString(), |
642 | equals('ConstraintsTransformBox(alignment: Alignment.center, constraints transform: unconstrained)' ), |
643 | ); |
644 | expect( |
645 | const ConstraintsTransformBox( |
646 | textDirection: TextDirection.rtl, |
647 | alignment: Alignment.topRight, |
648 | constraintsTransform: ConstraintsTransformBox.widthUnconstrained, |
649 | ).toString(), |
650 | equals('ConstraintsTransformBox(alignment: Alignment.topRight, textDirection: rtl, constraints transform: width constraints removed)' ), |
651 | ); |
652 | }); |
653 | }); |
654 | |
655 | group('ColoredBox' , () { |
656 | late _MockCanvas mockCanvas; |
657 | late _MockPaintingContext mockContext; |
658 | const Color colorToPaint = Color(0xFFABCDEF); |
659 | |
660 | setUp(() { |
661 | mockContext = _MockPaintingContext(); |
662 | mockCanvas = mockContext.canvas; |
663 | }); |
664 | |
665 | testWidgets('ColoredBox - no size, no child' , (WidgetTester tester) async { |
666 | await tester.pumpWidget(const Flex( |
667 | direction: Axis.horizontal, |
668 | textDirection: TextDirection.ltr, |
669 | children: <Widget>[ |
670 | SizedBox.shrink( |
671 | child: ColoredBox(color: colorToPaint), |
672 | ), |
673 | ], |
674 | )); |
675 | expect(find.byType(ColoredBox), findsOneWidget); |
676 | final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); |
677 | |
678 | renderColoredBox.paint(mockContext, Offset.zero); |
679 | |
680 | expect(mockCanvas.rects, isEmpty); |
681 | expect(mockCanvas.paints, isEmpty); |
682 | expect(mockContext.children, isEmpty); |
683 | expect(mockContext.offsets, isEmpty); |
684 | }); |
685 | |
686 | testWidgets('ColoredBox - no size, child' , (WidgetTester tester) async { |
687 | const ValueKey<int> key = ValueKey<int>(0); |
688 | const Widget child = SizedBox.expand(key: key); |
689 | await tester.pumpWidget(const Flex( |
690 | direction: Axis.horizontal, |
691 | textDirection: TextDirection.ltr, |
692 | children: <Widget>[ |
693 | SizedBox.shrink( |
694 | child: ColoredBox(color: colorToPaint, child: child), |
695 | ), |
696 | ], |
697 | )); |
698 | expect(find.byType(ColoredBox), findsOneWidget); |
699 | final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); |
700 | final RenderObject renderSizedBox = tester.renderObject(find.byKey(key)); |
701 | |
702 | renderColoredBox.paint(mockContext, Offset.zero); |
703 | |
704 | expect(mockCanvas.rects, isEmpty); |
705 | expect(mockCanvas.paints, isEmpty); |
706 | expect(mockContext.children.single, renderSizedBox); |
707 | expect(mockContext.offsets.single, Offset.zero); |
708 | }); |
709 | |
710 | testWidgets('ColoredBox - size, no child' , (WidgetTester tester) async { |
711 | await tester.pumpWidget(const ColoredBox(color: colorToPaint)); |
712 | expect(find.byType(ColoredBox), findsOneWidget); |
713 | final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); |
714 | |
715 | renderColoredBox.paint(mockContext, Offset.zero); |
716 | |
717 | expect(mockCanvas.rects.single, const Rect.fromLTWH(0, 0, 800, 600)); |
718 | expect(mockCanvas.paints.single.color, isSameColorAs(colorToPaint)); |
719 | expect(mockContext.children, isEmpty); |
720 | expect(mockContext.offsets, isEmpty); |
721 | }); |
722 | |
723 | testWidgets('ColoredBox - size, child' , (WidgetTester tester) async { |
724 | const ValueKey<int> key = ValueKey<int>(0); |
725 | const Widget child = SizedBox.expand(key: key); |
726 | await tester.pumpWidget(const ColoredBox(color: colorToPaint, child: child)); |
727 | expect(find.byType(ColoredBox), findsOneWidget); |
728 | final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); |
729 | final RenderObject renderSizedBox = tester.renderObject(find.byKey(key)); |
730 | |
731 | renderColoredBox.paint(mockContext, Offset.zero); |
732 | |
733 | expect(mockCanvas.rects.single, const Rect.fromLTWH(0, 0, 800, 600)); |
734 | expect(mockCanvas.paints.single.color, isSameColorAs(colorToPaint)); |
735 | expect(mockContext.children.single, renderSizedBox); |
736 | expect(mockContext.offsets.single, Offset.zero); |
737 | }); |
738 | |
739 | testWidgets('ColoredBox - debugFillProperties' , (WidgetTester tester) async { |
740 | const ColoredBox box = ColoredBox(color: colorToPaint); |
741 | final DiagnosticPropertiesBuilder properties = DiagnosticPropertiesBuilder(); |
742 | box.debugFillProperties(properties); |
743 | |
744 | expect(properties.properties.first.value, colorToPaint); |
745 | }); |
746 | }); |
747 | |
748 | testWidgets('Inconsequential golden test' , (WidgetTester tester) async { |
749 | // The test validates the Flutter Gold integration. Any changes to the |
750 | // golden file can be approved at any time. |
751 | await tester.pumpWidget(RepaintBoundary( |
752 | child: Container( |
753 | color: const Color(0xFF161145), |
754 | ), |
755 | )); |
756 | |
757 | await tester.pumpAndSettle(); |
758 | await expectLater( |
759 | find.byType(RepaintBoundary), |
760 | matchesGoldenFile('inconsequential_golden_file.png' ), |
761 | ); |
762 | }); |
763 | |
764 | testWidgets('IgnorePointer ignores pointers' , (WidgetTester tester) async { |
765 | final List<String> logs = <String>[]; |
766 | Widget target({required bool ignoring}) => Align( |
767 | alignment: Alignment.topLeft, |
768 | child: Directionality( |
769 | textDirection: TextDirection.ltr, |
770 | child: SizedBox( |
771 | width: 100, |
772 | height: 100, |
773 | child: Listener( |
774 | onPointerDown: (_) { logs.add('down1' ); }, |
775 | child: MouseRegion( |
776 | onEnter: (_) { logs.add('enter1' ); }, |
777 | onExit: (_) { logs.add('exit1' ); }, |
778 | cursor: SystemMouseCursors.forbidden, |
779 | child: Stack( |
780 | children: <Widget>[ |
781 | Listener( |
782 | onPointerDown: (_) { logs.add('down2' ); }, |
783 | child: MouseRegion( |
784 | cursor: SystemMouseCursors.click, |
785 | onEnter: (_) { logs.add('enter2' ); }, |
786 | onExit: (_) { logs.add('exit2' ); }, |
787 | ), |
788 | ), |
789 | IgnorePointer( |
790 | ignoring: ignoring, |
791 | child: Listener( |
792 | onPointerDown: (_) { logs.add('down3' ); }, |
793 | child: MouseRegion( |
794 | cursor: SystemMouseCursors.text, |
795 | onEnter: (_) { logs.add('enter3' ); }, |
796 | onExit: (_) { logs.add('exit3' ); }, |
797 | ), |
798 | ), |
799 | ), |
800 | ], |
801 | ), |
802 | ), |
803 | ), |
804 | ), |
805 | ), |
806 | ); |
807 | |
808 | final TestGesture gesture = await tester.createGesture(pointer: 1, kind: PointerDeviceKind.mouse); |
809 | await gesture.addPointer(location: const Offset(200, 200)); |
810 | |
811 | await tester.pumpWidget(target(ignoring: true)); |
812 | expect(logs, isEmpty); |
813 | |
814 | await gesture.moveTo(const Offset(50, 50)); |
815 | expect(logs, <String>['enter1' , 'enter2' ]); |
816 | logs.clear(); |
817 | |
818 | await gesture.down(const Offset(50, 50)); |
819 | expect(logs, <String>['down2' , 'down1' ]); |
820 | logs.clear(); |
821 | |
822 | await gesture.up(); |
823 | expect(logs, isEmpty); |
824 | |
825 | await tester.pumpWidget(target(ignoring: false)); |
826 | expect(logs, <String>['exit2' , 'enter3' ]); |
827 | logs.clear(); |
828 | |
829 | await gesture.down(const Offset(50, 50)); |
830 | expect(logs, <String>['down3' , 'down1' ]); |
831 | logs.clear(); |
832 | |
833 | await gesture.up(); |
834 | expect(logs, isEmpty); |
835 | |
836 | await tester.pumpWidget(target(ignoring: true)); |
837 | expect(logs, <String>['exit3' , 'enter2' ]); |
838 | logs.clear(); |
839 | }); |
840 | |
841 | group('IgnorePointer semantics' , () { |
842 | testWidgets('does not change semantics when not ignoring' , (WidgetTester tester) async { |
843 | final UniqueKey key = UniqueKey(); |
844 | await tester.pumpWidget( |
845 | MaterialApp( |
846 | home: IgnorePointer( |
847 | ignoring: false, |
848 | child: ElevatedButton( |
849 | key: key, |
850 | onPressed: () { }, |
851 | child: const Text('button' ), |
852 | ), |
853 | ), |
854 | ), |
855 | ); |
856 | expect( |
857 | tester.getSemantics(find.byKey(key)), |
858 | matchesSemantics( |
859 | label: 'button' , |
860 | hasTapAction: true, |
861 | hasFocusAction: true, |
862 | isButton: true, |
863 | isFocusable: true, |
864 | hasEnabledState: true, |
865 | isEnabled: true, |
866 | ), |
867 | ); |
868 | }); |
869 | |
870 | testWidgets('can toggle the ignoring.' , (WidgetTester tester) async { |
871 | final UniqueKey key1 = UniqueKey(); |
872 | final UniqueKey key2 = UniqueKey(); |
873 | final UniqueKey key3 = UniqueKey(); |
874 | await tester.pumpWidget( |
875 | MaterialApp( |
876 | home: TestIgnorePointer( |
877 | child: Semantics( |
878 | key: key1, |
879 | label: '1' , |
880 | onTap: (){ }, |
881 | container: true, |
882 | child: Semantics( |
883 | key: key2, |
884 | label: '2' , |
885 | onTap: (){ }, |
886 | container: true, |
887 | child: Semantics( |
888 | key: key3, |
889 | label: '3' , |
890 | onTap: (){ }, |
891 | container: true, |
892 | child: const SizedBox(width: 10, height: 10), |
893 | ), |
894 | ), |
895 | ), |
896 | ), |
897 | ), |
898 | ); |
899 | expect( |
900 | tester.getSemantics(find.byKey(key1)), |
901 | matchesSemantics( |
902 | label: '1' , |
903 | ), |
904 | ); |
905 | expect( |
906 | tester.getSemantics(find.byKey(key2)), |
907 | matchesSemantics( |
908 | label: '2' , |
909 | ), |
910 | ); |
911 | expect( |
912 | tester.getSemantics(find.byKey(key3)), |
913 | matchesSemantics( |
914 | label: '3' , |
915 | ), |
916 | ); |
917 | |
918 | final TestIgnorePointerState state = tester.state<TestIgnorePointerState>(find.byType(TestIgnorePointer)); |
919 | state.setIgnore(false); |
920 | await tester.pump(); |
921 | expect( |
922 | tester.getSemantics(find.byKey(key1)), |
923 | matchesSemantics( |
924 | label: '1' , |
925 | hasTapAction: true, |
926 | ), |
927 | ); |
928 | expect( |
929 | tester.getSemantics(find.byKey(key2)), |
930 | matchesSemantics( |
931 | label: '2' , |
932 | hasTapAction: true, |
933 | ), |
934 | ); |
935 | expect( |
936 | tester.getSemantics(find.byKey(key3)), |
937 | matchesSemantics( |
938 | label: '3' , |
939 | hasTapAction: true, |
940 | ), |
941 | ); |
942 | |
943 | state.setIgnore(true); |
944 | await tester.pump(); |
945 | expect( |
946 | tester.getSemantics(find.byKey(key1)), |
947 | matchesSemantics( |
948 | label: '1' , |
949 | ), |
950 | ); |
951 | expect( |
952 | tester.getSemantics(find.byKey(key2)), |
953 | matchesSemantics( |
954 | label: '2' , |
955 | ), |
956 | ); |
957 | expect( |
958 | tester.getSemantics(find.byKey(key3)), |
959 | matchesSemantics( |
960 | label: '3' , |
961 | ), |
962 | ); |
963 | |
964 | state.setIgnore(false); |
965 | await tester.pump(); |
966 | expect( |
967 | tester.getSemantics(find.byKey(key1)), |
968 | matchesSemantics( |
969 | label: '1' , |
970 | hasTapAction: true, |
971 | ), |
972 | ); |
973 | expect( |
974 | tester.getSemantics(find.byKey(key2)), |
975 | matchesSemantics( |
976 | label: '2' , |
977 | hasTapAction: true, |
978 | ), |
979 | ); |
980 | expect( |
981 | tester.getSemantics(find.byKey(key3)), |
982 | matchesSemantics( |
983 | label: '3' , |
984 | hasTapAction: true, |
985 | ), |
986 | ); |
987 | }); |
988 | |
989 | testWidgets('drops semantics when its ignoringSemantics is true' , (WidgetTester tester) async { |
990 | final SemanticsTester semantics = SemanticsTester(tester); |
991 | final UniqueKey key = UniqueKey(); |
992 | await tester.pumpWidget( |
993 | MaterialApp( |
994 | home: IgnorePointer( |
995 | ignoringSemantics: true, |
996 | child: ElevatedButton( |
997 | key: key, |
998 | onPressed: () { }, |
999 | child: const Text('button' ), |
1000 | ), |
1001 | ), |
1002 | ), |
1003 | ); |
1004 | expect(semantics, isNot(includesNodeWith(label: 'button' ))); |
1005 | semantics.dispose(); |
1006 | }); |
1007 | |
1008 | testWidgets('ignores user interactions' , (WidgetTester tester) async { |
1009 | final UniqueKey key = UniqueKey(); |
1010 | await tester.pumpWidget( |
1011 | MaterialApp( |
1012 | home: IgnorePointer( |
1013 | child: ElevatedButton( |
1014 | key: key, |
1015 | onPressed: () { }, |
1016 | child: const Text('button' ), |
1017 | ), |
1018 | ), |
1019 | ), |
1020 | ); |
1021 | expect( |
1022 | tester.getSemantics(find.byKey(key)), |
1023 | // Tap action is blocked. |
1024 | matchesSemantics( |
1025 | label: 'button' , |
1026 | isButton: true, |
1027 | isFocusable: true, |
1028 | hasEnabledState: true, |
1029 | isEnabled: true, |
1030 | ), |
1031 | ); |
1032 | }); |
1033 | }); |
1034 | |
1035 | testWidgets('AbsorbPointer absorbs pointers' , (WidgetTester tester) async { |
1036 | final List<String> logs = <String>[]; |
1037 | Widget target({required bool absorbing}) => Align( |
1038 | alignment: Alignment.topLeft, |
1039 | child: Directionality( |
1040 | textDirection: TextDirection.ltr, |
1041 | child: SizedBox( |
1042 | width: 100, |
1043 | height: 100, |
1044 | child: Listener( |
1045 | onPointerDown: (_) { logs.add('down1' ); }, |
1046 | child: MouseRegion( |
1047 | onEnter: (_) { logs.add('enter1' ); }, |
1048 | onExit: (_) { logs.add('exit1' ); }, |
1049 | cursor: SystemMouseCursors.forbidden, |
1050 | child: Stack( |
1051 | children: <Widget>[ |
1052 | Listener( |
1053 | onPointerDown: (_) { logs.add('down2' ); }, |
1054 | child: MouseRegion( |
1055 | cursor: SystemMouseCursors.click, |
1056 | onEnter: (_) { logs.add('enter2' ); }, |
1057 | onExit: (_) { logs.add('exit2' ); }, |
1058 | ), |
1059 | ), |
1060 | AbsorbPointer( |
1061 | absorbing: absorbing, |
1062 | child: Listener( |
1063 | onPointerDown: (_) { logs.add('down3' ); }, |
1064 | child: MouseRegion( |
1065 | cursor: SystemMouseCursors.text, |
1066 | onEnter: (_) { logs.add('enter3' ); }, |
1067 | onExit: (_) { logs.add('exit3' ); }, |
1068 | ), |
1069 | ), |
1070 | ), |
1071 | ], |
1072 | ), |
1073 | ), |
1074 | ), |
1075 | ), |
1076 | ), |
1077 | ); |
1078 | |
1079 | final TestGesture gesture = await tester.createGesture(pointer: 1, kind: PointerDeviceKind.mouse); |
1080 | await gesture.addPointer(location: const Offset(200, 200)); |
1081 | |
1082 | await tester.pumpWidget(target(absorbing: true)); |
1083 | expect(logs, isEmpty); |
1084 | |
1085 | await gesture.moveTo(const Offset(50, 50)); |
1086 | expect(logs, <String>['enter1' ]); |
1087 | logs.clear(); |
1088 | |
1089 | await gesture.down(const Offset(50, 50)); |
1090 | expect(logs, <String>['down1' ]); |
1091 | logs.clear(); |
1092 | |
1093 | await gesture.up(); |
1094 | expect(logs, isEmpty); |
1095 | |
1096 | await tester.pumpWidget(target(absorbing: false)); |
1097 | expect(logs, <String>['enter3' ]); |
1098 | logs.clear(); |
1099 | |
1100 | await gesture.down(const Offset(50, 50)); |
1101 | expect(logs, <String>['down3' , 'down1' ]); |
1102 | logs.clear(); |
1103 | |
1104 | await gesture.up(); |
1105 | expect(logs, isEmpty); |
1106 | |
1107 | await tester.pumpWidget(target(absorbing: true)); |
1108 | expect(logs, <String>['exit3' ]); |
1109 | logs.clear(); |
1110 | }); |
1111 | |
1112 | testWidgets('Wrap implements debugFillProperties' , (WidgetTester tester) async { |
1113 | final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
1114 | const Wrap( |
1115 | spacing: 8.0, // gap between adjacent Text widget |
1116 | runSpacing: 4.0, // gap between lines |
1117 | textDirection: TextDirection.ltr, |
1118 | verticalDirection: VerticalDirection.up, |
1119 | children: <Widget>[ |
1120 | Text('Hamilton' ), |
1121 | Text('Lafayette' ), |
1122 | Text('Mulligan' ), |
1123 | ], |
1124 | ).debugFillProperties(builder); |
1125 | |
1126 | final List<String> description = builder.properties |
1127 | .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) |
1128 | .map((DiagnosticsNode node) => node.toString()) |
1129 | .toList(); |
1130 | |
1131 | expect(description, unorderedMatches(<dynamic>[ |
1132 | contains('direction: horizontal' ), |
1133 | contains('alignment: start' ), |
1134 | contains('spacing: 8.0' ), |
1135 | contains('runAlignment: start' ), |
1136 | contains('runSpacing: 4.0' ), |
1137 | contains('crossAxisAlignment: start' ), |
1138 | contains('textDirection: ltr' ), |
1139 | contains('verticalDirection: up' ), |
1140 | ])); |
1141 | }); |
1142 | |
1143 | testWidgets('Row and IgnoreBaseline (control -- with baseline)' , (WidgetTester tester) async { |
1144 | await tester.pumpWidget( |
1145 | const Row( |
1146 | crossAxisAlignment: CrossAxisAlignment.baseline, |
1147 | textBaseline: TextBaseline.alphabetic, |
1148 | textDirection: TextDirection.ltr, |
1149 | children: <Widget>[ |
1150 | Text( |
1151 | 'a' , |
1152 | textDirection: TextDirection.ltr, |
1153 | style: TextStyle(fontSize: 128.0, fontFamily: 'FlutterTest' ), // places baseline at y=96 |
1154 | ), |
1155 | Text( |
1156 | 'b' , |
1157 | textDirection: TextDirection.ltr, |
1158 | style: TextStyle(fontSize: 32.0, fontFamily: 'FlutterTest' ), // 24 above baseline, 8 below baseline |
1159 | ), |
1160 | ], |
1161 | ), |
1162 | ); |
1163 | |
1164 | final Offset aPos = tester.getTopLeft(find.text('a' )); |
1165 | final Offset bPos = tester.getTopLeft(find.text('b' )); |
1166 | expect(aPos.dy, 0.0); |
1167 | expect(bPos.dy, 96.0 - 24.0); |
1168 | }); |
1169 | |
1170 | testWidgets('Row and IgnoreBaseline (with ignored baseline)' , (WidgetTester tester) async { |
1171 | await tester.pumpWidget( |
1172 | const Row( |
1173 | crossAxisAlignment: CrossAxisAlignment.baseline, |
1174 | textBaseline: TextBaseline.alphabetic, |
1175 | textDirection: TextDirection.ltr, |
1176 | children: <Widget>[ |
1177 | IgnoreBaseline( |
1178 | child: Text( |
1179 | 'a' , |
1180 | textDirection: TextDirection.ltr, |
1181 | style: TextStyle(fontSize: 128.0, fontFamily: 'FlutterTest' ), // places baseline at y=96 |
1182 | ), |
1183 | ), |
1184 | Text( |
1185 | 'b' , |
1186 | textDirection: TextDirection.ltr, |
1187 | style: TextStyle(fontSize: 32.0, fontFamily: 'FlutterTest' ), // 24 above baseline, 8 below baseline |
1188 | ), |
1189 | ], |
1190 | ), |
1191 | ); |
1192 | |
1193 | final Offset aPos = tester.getTopLeft(find.text('a' )); |
1194 | final Offset bPos = tester.getTopLeft(find.text('b' )); |
1195 | expect(aPos.dy, 0.0); |
1196 | expect(bPos.dy, 0.0); |
1197 | }); |
1198 | } |
1199 | |
1200 | HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox); |
1201 | |
1202 | class HitsRenderBox extends Matcher { |
1203 | const HitsRenderBox(this.renderBox); |
1204 | |
1205 | final RenderBox renderBox; |
1206 | |
1207 | @override |
1208 | Description describe(Description description) => |
1209 | description.add('hit test result contains ' ).addDescriptionOf(renderBox); |
1210 | |
1211 | @override |
1212 | bool matches(dynamic item, Map<dynamic, dynamic> matchState) { |
1213 | final HitTestResult hitTestResult = item as HitTestResult; |
1214 | return hitTestResult.path.where( |
1215 | (HitTestEntry entry) => entry.target == renderBox, |
1216 | ).isNotEmpty; |
1217 | } |
1218 | } |
1219 | |
1220 | DoesNotHitRenderBox doesNotHit(RenderBox renderBox) => DoesNotHitRenderBox(renderBox); |
1221 | |
1222 | class DoesNotHitRenderBox extends Matcher { |
1223 | const DoesNotHitRenderBox(this.renderBox); |
1224 | |
1225 | final RenderBox renderBox; |
1226 | |
1227 | @override |
1228 | Description describe(Description description) => |
1229 | description.add("hit test result doesn't contain " ).addDescriptionOf(renderBox); |
1230 | |
1231 | @override |
1232 | bool matches(dynamic item, Map<dynamic, dynamic> matchState) { |
1233 | final HitTestResult hitTestResult = item as HitTestResult; |
1234 | return hitTestResult.path.where( |
1235 | (HitTestEntry entry) => entry.target == renderBox, |
1236 | ).isEmpty; |
1237 | } |
1238 | } |
1239 | |
1240 | class _MockPaintingContext extends Fake implements PaintingContext { |
1241 | final List<RenderObject> children = <RenderObject>[]; |
1242 | final List<Offset> offsets = <Offset>[]; |
1243 | |
1244 | @override |
1245 | final _MockCanvas canvas = _MockCanvas(); |
1246 | |
1247 | @override |
1248 | void paintChild(RenderObject child, Offset offset) { |
1249 | children.add(child); |
1250 | offsets.add(offset); |
1251 | } |
1252 | } |
1253 | |
1254 | class _MockCanvas extends Fake implements Canvas { |
1255 | final List<Rect> rects = <Rect>[]; |
1256 | final List<Paint> paints = <Paint>[]; |
1257 | bool didPaint = false; |
1258 | |
1259 | @override |
1260 | void drawRect(Rect rect, Paint paint) { |
1261 | rects.add(rect); |
1262 | paints.add(paint); |
1263 | } |
1264 | } |
1265 | |
1266 | |
1267 | class TestIgnorePointer extends StatefulWidget { |
1268 | const TestIgnorePointer({super.key, required this.child}); |
1269 | |
1270 | final Widget child; |
1271 | @override |
1272 | State<StatefulWidget> createState() => TestIgnorePointerState(); |
1273 | } |
1274 | |
1275 | class TestIgnorePointerState extends State<TestIgnorePointer> { |
1276 | bool ignore = true; |
1277 | |
1278 | void setIgnore(bool newIgnore) { |
1279 | setState(() { |
1280 | ignore = newIgnore; |
1281 | }); |
1282 | } |
1283 | |
1284 | @override |
1285 | Widget build(BuildContext context) { |
1286 | return IgnorePointer( |
1287 | ignoring: ignore, |
1288 | child: widget.child, |
1289 | ); |
1290 | } |
1291 | } |
1292 | |