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'])
8library;
9
10import 'dart:math' as math;
11import 'dart:ui' as ui;
12import 'dart:ui';
13
14import 'package:flutter/gestures.dart';
15import 'package:flutter/material.dart';
16import 'package:flutter/rendering.dart';
17import 'package:flutter_test/flutter_test.dart';
18
19import 'semantics_tester.dart';
20
21void 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
1200HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox);
1201
1202class 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
1220DoesNotHitRenderBox doesNotHit(RenderBox renderBox) => DoesNotHitRenderBox(renderBox);
1221
1222class 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
1240class _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
1254class _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
1267class 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
1275class 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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com