// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file is run as part of a reduced test set in CI on Mac and Windows // machines. @Tags(<String>['reduced-test-set']) import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('RawImage', () { testWidgets('properties', (WidgetTester tester) async { final ui.Image image1 = (await tester.runAsync<ui.Image>(() => createTestImage()))!; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: RawImage(image: image1), ), ); final RenderImage renderObject = tester.firstRenderObject<RenderImage>(find.byType(RawImage)); // Expect default values expect(renderObject.image!.isCloneOf(image1), true); expect(renderObject.debugImageLabel, null); expect(renderObject.width, null); expect(renderObject.height, null); expect(renderObject.scale, 1.0); expect(renderObject.color, null); expect(renderObject.opacity, null); expect(renderObject.colorBlendMode, null); expect(renderObject.fit, null); expect(renderObject.alignment, Alignment.center); expect(renderObject.repeat, ImageRepeat.noRepeat); expect(renderObject.centerSlice, null); expect(renderObject.matchTextDirection, false); expect(renderObject.invertColors, false); expect(renderObject.filterQuality, FilterQuality.low); expect(renderObject.isAntiAlias, false); final ui.Image image2 = (await tester.runAsync<ui.Image>(() => createTestImage(width: 2, height: 2)))!; const String debugImageLabel = 'debugImageLabel'; const double width = 1; const double height = 1; const double scale = 2.0; const Color color = Colors.black; const Animation<double> opacity = AlwaysStoppedAnimation<double>(0.0); const BlendMode colorBlendMode = BlendMode.difference; const BoxFit fit = BoxFit.contain; const AlignmentGeometry alignment = Alignment.topCenter; const ImageRepeat repeat = ImageRepeat.repeat; const Rect centerSlice = Rect.fromLTWH(0, 0, width, height); const bool matchTextDirection = true; const bool invertColors = true; const FilterQuality filterQuality = FilterQuality.high; const bool isAntiAlias = true; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: RawImage( image: image2, debugImageLabel: debugImageLabel, width: width, height: height, scale: scale, color: color, opacity: opacity, colorBlendMode: colorBlendMode, fit: fit, alignment: alignment, repeat: repeat, centerSlice: centerSlice, matchTextDirection: matchTextDirection, invertColors: invertColors, filterQuality: filterQuality, isAntiAlias: isAntiAlias, ), ), ); expect(renderObject.image!.isCloneOf(image2), true); expect(renderObject.debugImageLabel, debugImageLabel); expect(renderObject.width, width); expect(renderObject.height, height); expect(renderObject.scale, scale); expect(renderObject.color, color); expect(renderObject.opacity, opacity); expect(renderObject.colorBlendMode, colorBlendMode); expect(renderObject.fit, fit); expect(renderObject.alignment, alignment); expect(renderObject.repeat, repeat); expect(renderObject.centerSlice, centerSlice); expect(renderObject.matchTextDirection, matchTextDirection); expect(renderObject.invertColors, invertColors); expect(renderObject.filterQuality, filterQuality); expect(renderObject.isAntiAlias, isAntiAlias); }); }); group('PhysicalShape', () { testWidgets('properties', (WidgetTester tester) async { await tester.pumpWidget( const PhysicalShape( clipper: ShapeBorderClipper(shape: CircleBorder()), elevation: 2.0, color: Color(0xFF0000FF), shadowColor: Color(0xFF00FF00), ), ); final RenderPhysicalShape renderObject = tester.renderObject(find.byType(PhysicalShape)); expect(renderObject.clipper, const ShapeBorderClipper(shape: CircleBorder())); expect(renderObject.color, const Color(0xFF0000FF)); expect(renderObject.shadowColor, const Color(0xFF00FF00)); expect(renderObject.elevation, 2.0); }); testWidgets('hit test', (WidgetTester tester) async { await tester.pumpWidget( PhysicalShape( clipper: const ShapeBorderClipper(shape: CircleBorder()), elevation: 2.0, color: const Color(0xFF0000FF), shadowColor: const Color(0xFF00FF00), child: Container(color: const Color(0xFF0000FF)), ), ); final RenderPhysicalShape renderPhysicalShape = tester.renderObject(find.byType(PhysicalShape)); // The viewport is 800x600, the CircleBorder is centered and fits // the shortest edge, so we get a circle of radius 300, centered at // (400, 300). // // We test by sampling a few points around the left-most point of the // circle (100, 300). expect(tester.hitTestOnBinding(const Offset(99.0, 300.0)), doesNotHit(renderPhysicalShape)); expect(tester.hitTestOnBinding(const Offset(100.0, 300.0)), hits(renderPhysicalShape)); expect(tester.hitTestOnBinding(const Offset(100.0, 299.0)), doesNotHit(renderPhysicalShape)); expect(tester.hitTestOnBinding(const Offset(100.0, 301.0)), doesNotHit(renderPhysicalShape)); }); }); group('FractionalTranslation', () { testWidgets('hit test - entirely inside the bounding box', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); bool _pointerDown = false; await tester.pumpWidget( Center( child: FractionalTranslation( translation: Offset.zero, child: Listener( onPointerDown: (PointerDownEvent event) { _pointerDown = true; }, child: SizedBox( key: key1, width: 100.0, height: 100.0, child: Container( color: const Color(0xFF0000FF), ), ), ), ), ), ); expect(_pointerDown, isFalse); await tester.tap(find.byKey(key1)); expect(_pointerDown, isTrue); }); testWidgets('hit test - partially inside the bounding box', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); bool _pointerDown = false; await tester.pumpWidget( Center( child: FractionalTranslation( translation: const Offset(0.5, 0.5), child: Listener( onPointerDown: (PointerDownEvent event) { _pointerDown = true; }, child: SizedBox( key: key1, width: 100.0, height: 100.0, child: Container( color: const Color(0xFF0000FF), ), ), ), ), ), ); expect(_pointerDown, isFalse); await tester.tap(find.byKey(key1)); expect(_pointerDown, isTrue); }); testWidgets('hit test - completely outside the bounding box', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); bool _pointerDown = false; await tester.pumpWidget( Center( child: FractionalTranslation( translation: const Offset(1.0, 1.0), child: Listener( onPointerDown: (PointerDownEvent event) { _pointerDown = true; }, child: SizedBox( key: key1, width: 100.0, height: 100.0, child: Container( color: const Color(0xFF0000FF), ), ), ), ), ), ); expect(_pointerDown, isFalse); await tester.tap(find.byKey(key1)); expect(_pointerDown, isTrue); }); testWidgets('semantics bounds are updated', (WidgetTester tester) async { final GlobalKey fractionalTranslationKey = GlobalKey(); final GlobalKey textKey = GlobalKey(); Offset offset = const Offset(0.4, 0.4); await tester.pumpWidget( StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Directionality( textDirection: TextDirection.ltr, child: Center( child: Semantics( explicitChildNodes: true, child: FractionalTranslation( key: fractionalTranslationKey, translation: offset, child: GestureDetector( onTap: () { setState(() { offset = const Offset(0.8, 0.8); }); }, child: SizedBox( width: 100.0, height: 100.0, child: Text( 'foo', key: textKey, ), ), ), ), ), ), ); }, ), ); expect( tester.getSemantics(find.byKey(textKey)).transform, Matrix4( 3.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1170.0, 870.0, 0.0, 1.0, ), ); await tester.tap(find.byKey(fractionalTranslationKey), warnIfMissed: false); // RenderFractionalTranslation can't be hit await tester.pump(); expect( tester.getSemantics(find.byKey(textKey)).transform, Matrix4( 3.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1290.0, 990.0, 0.0, 1.0, ), ); }); }); group('Semantics', () { testWidgets('Semantics can set attributed Text', (WidgetTester tester) async { final UniqueKey key = UniqueKey(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Semantics( key: key, attributedLabel: AttributedString( 'label', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), ], ), attributedValue: AttributedString( 'value', attributes: <StringAttribute>[ LocaleStringAttribute(range: const TextRange(start: 0, end: 5), locale: const Locale('en', 'MX')), ], ), attributedHint: AttributedString( 'hint', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)), ], ), child: const Placeholder(), ) ), ) ); final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel; expect(attributedLabel.string, 'label'); expect(attributedLabel.attributes.length, 1); expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue); expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5)); final AttributedString attributedValue = tester.getSemantics(find.byKey(key)).attributedValue; expect(attributedValue.string, 'value'); expect(attributedValue.attributes.length, 1); expect(attributedValue.attributes[0] is LocaleStringAttribute, isTrue); final LocaleStringAttribute valueLocale = attributedValue.attributes[0] as LocaleStringAttribute; expect(valueLocale.range, const TextRange(start:0, end: 5)); expect(valueLocale.locale, const Locale('en', 'MX')); final AttributedString attributedHint = tester.getSemantics(find.byKey(key)).attributedHint; expect(attributedHint.string, 'hint'); expect(attributedHint.attributes.length, 1); expect(attributedHint.attributes[0] is SpellOutStringAttribute, isTrue); expect(attributedHint.attributes[0].range, const TextRange(start:1, end: 2)); }); testWidgets('Semantics can merge attributed strings', (WidgetTester tester) async { final UniqueKey key = UniqueKey(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Semantics( key: key, attributedLabel: AttributedString( 'label', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), ], ), attributedHint: AttributedString( 'hint', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)), ], ), child: Semantics( attributedLabel: AttributedString( 'label', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), ], ), attributedHint: AttributedString( 'hint', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)), ], ), child: const Placeholder(), ) ) ), ) ); final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel; expect(attributedLabel.string, 'label\nlabel'); expect(attributedLabel.attributes.length, 2); expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue); expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5)); expect(attributedLabel.attributes[1] is SpellOutStringAttribute, isTrue); expect(attributedLabel.attributes[1].range, const TextRange(start:6, end: 11)); final AttributedString attributedHint = tester.getSemantics(find.byKey(key)).attributedHint; expect(attributedHint.string, 'hint\nhint'); expect(attributedHint.attributes.length, 2); expect(attributedHint.attributes[0] is SpellOutStringAttribute, isTrue); expect(attributedHint.attributes[0].range, const TextRange(start:1, end: 2)); expect(attributedHint.attributes[1] is SpellOutStringAttribute, isTrue); expect(attributedHint.attributes[1].range, const TextRange(start:6, end: 7)); }); testWidgets('Semantics can merge attributed strings with non attributed string', (WidgetTester tester) async { final UniqueKey key = UniqueKey(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Semantics( key: key, attributedLabel: AttributedString( 'label1', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)), ], ), child: Semantics( label: 'label2', child: Semantics( attributedLabel: AttributedString( 'label3', attributes: <StringAttribute>[ SpellOutStringAttribute(range: const TextRange(start: 1, end: 3)), ], ), child: const Placeholder(), ), ) ) ), ) ); final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel; expect(attributedLabel.string, 'label1\nlabel2\nlabel3'); expect(attributedLabel.attributes.length, 2); expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue); expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5)); expect(attributedLabel.attributes[1] is SpellOutStringAttribute, isTrue); expect(attributedLabel.attributes[1].range, const TextRange(start:15, end: 17)); }); }); group('Row', () { testWidgets('multiple baseline aligned children', (WidgetTester tester) async { final UniqueKey key1 = UniqueKey(); final UniqueKey key2 = UniqueKey(); const double fontSize1 = 54; const double fontSize2 = 14; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: <Widget>[ Text('big text', key: key1, style: const TextStyle(fontSize: fontSize1), ), Text('one\ntwo\nthree\nfour\nfive\nsix\nseven', key: key2, style: const TextStyle(fontSize: fontSize2), ), ], ), ), ), ); final RenderBox textBox1 = tester.renderObject(find.byKey(key1)); final RenderBox textBox2 = tester.renderObject(find.byKey(key2)); final RenderBox rowBox = tester.renderObject(find.byType(Row)); // The two Texts are baseline aligned, so some portion of them extends // both above and below the baseline. The first has a huge font size, so // it extends higher above the baseline than usual. The second has many // lines, but being aligned by the first line's baseline, they hang far // below the baseline. The size of the parent row is just enough to // contain both of them. const double ahemBaselineLocation = 0.8; // https://web-platform-tests.org/writing-tests/ahem.html const double aboveBaseline1 = fontSize1 * ahemBaselineLocation; const double belowBaseline1 = fontSize1 * (1 - ahemBaselineLocation); const double aboveBaseline2 = fontSize2 * ahemBaselineLocation; const double belowBaseline2 = fontSize2 * (1 - ahemBaselineLocation) + fontSize2 * 6; final double aboveBaseline = math.max(aboveBaseline1, aboveBaseline2); final double belowBaseline = math.max(belowBaseline1, belowBaseline2); expect(rowBox.size.height, greaterThan(textBox1.size.height)); expect(rowBox.size.height, greaterThan(textBox2.size.height)); expect(rowBox.size.height, moreOrLessEquals(aboveBaseline + belowBaseline, epsilon: .001)); expect(tester.getTopLeft(find.byKey(key1)).dy, 0); expect( tester.getTopLeft(find.byKey(key2)).dy, moreOrLessEquals(aboveBaseline1 - aboveBaseline2, epsilon: .001), ); }); testWidgets('baseline aligned children account for a larger, no-baseline child size', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/58898 final UniqueKey key1 = UniqueKey(); final UniqueKey key2 = UniqueKey(); const double fontSize1 = 54; const double fontSize2 = 14; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: <Widget>[ Text('big text', key: key1, style: const TextStyle(fontSize: fontSize1), ), Text('one\ntwo\nthree\nfour\nfive\nsix\nseven', key: key2, style: const TextStyle(fontSize: fontSize2), ), const FlutterLogo(size: 250), ], ), ), ), ); final RenderBox textBox1 = tester.renderObject(find.byKey(key1)); final RenderBox textBox2 = tester.renderObject(find.byKey(key2)); final RenderBox rowBox = tester.renderObject(find.byType(Row)); // The two Texts are baseline aligned, so some portion of them extends // both above and below the baseline. The first has a huge font size, so // it extends higher above the baseline than usual. The second has many // lines, but being aligned by the first line's baseline, they hang far // below the baseline. The FlutterLogo extends further than both Texts, // so the size of the parent row should contain the FlutterLogo as well. const double ahemBaselineLocation = 0.8; // https://web-platform-tests.org/writing-tests/ahem.html const double aboveBaseline1 = fontSize1 * ahemBaselineLocation; const double aboveBaseline2 = fontSize2 * ahemBaselineLocation; expect(rowBox.size.height, greaterThan(textBox1.size.height)); expect(rowBox.size.height, greaterThan(textBox2.size.height)); expect(rowBox.size.height, 250); expect(tester.getTopLeft(find.byKey(key1)).dy, 0); expect( tester.getTopLeft(find.byKey(key2)).dy, moreOrLessEquals(aboveBaseline1 - aboveBaseline2, epsilon: .001), ); }); }); test('UnconstrainedBox toString', () { expect( const UnconstrainedBox(constrainedAxis: Axis.vertical).toString(), equals('UnconstrainedBox(alignment: Alignment.center, constrainedAxis: vertical)'), ); expect( const UnconstrainedBox(constrainedAxis: Axis.horizontal, textDirection: TextDirection.rtl, alignment: Alignment.topRight).toString(), equals('UnconstrainedBox(alignment: Alignment.topRight, constrainedAxis: horizontal, textDirection: rtl)'), ); }); testWidgets('UnconstrainedBox can set and update clipBehavior', (WidgetTester tester) async { await tester.pumpWidget(const UnconstrainedBox()); final RenderConstraintsTransformBox renderObject = tester.allRenderObjects.whereType<RenderConstraintsTransformBox>().first; expect(renderObject.clipBehavior, equals(Clip.none)); await tester.pumpWidget(const UnconstrainedBox(clipBehavior: Clip.antiAlias)); expect(renderObject.clipBehavior, equals(Clip.antiAlias)); }); group('ConstraintsTransformBox', () { test('toString', () { expect( const ConstraintsTransformBox( constraintsTransform: ConstraintsTransformBox.unconstrained, ).toString(), equals('ConstraintsTransformBox(alignment: Alignment.center, constraints transform: unconstrained)'), ); expect( const ConstraintsTransformBox( textDirection: TextDirection.rtl, alignment: Alignment.topRight, constraintsTransform: ConstraintsTransformBox.widthUnconstrained, ).toString(), equals('ConstraintsTransformBox(alignment: Alignment.topRight, textDirection: rtl, constraints transform: width constraints removed)'), ); }); }); group('ColoredBox', () { late _MockCanvas mockCanvas; late _MockPaintingContext mockContext; const Color colorToPaint = Color(0xFFABCDEF); setUp(() { mockContext = _MockPaintingContext(); mockCanvas = mockContext.canvas; }); testWidgets('ColoredBox - no size, no child', (WidgetTester tester) async { await tester.pumpWidget(Flex( direction: Axis.horizontal, textDirection: TextDirection.ltr, children: const <Widget>[ SizedBox.shrink( child: ColoredBox(color: colorToPaint), ), ], )); expect(find.byType(ColoredBox), findsOneWidget); final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); renderColoredBox.paint(mockContext, Offset.zero); expect(mockCanvas.rects, isEmpty); expect(mockCanvas.paints, isEmpty); expect(mockContext.children, isEmpty); expect(mockContext.offsets, isEmpty); }); testWidgets('ColoredBox - no size, child', (WidgetTester tester) async { const ValueKey<int> key = ValueKey<int>(0); const Widget child = SizedBox.expand(key: key); await tester.pumpWidget(Flex( direction: Axis.horizontal, textDirection: TextDirection.ltr, children: const <Widget>[ SizedBox.shrink( child: ColoredBox(color: colorToPaint, child: child), ), ], )); expect(find.byType(ColoredBox), findsOneWidget); final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); final RenderObject renderSizedBox = tester.renderObject(find.byKey(key)); renderColoredBox.paint(mockContext, Offset.zero); expect(mockCanvas.rects, isEmpty); expect(mockCanvas.paints, isEmpty); expect(mockContext.children.single, renderSizedBox); expect(mockContext.offsets.single, Offset.zero); }); testWidgets('ColoredBox - size, no child', (WidgetTester tester) async { await tester.pumpWidget(const ColoredBox(color: colorToPaint)); expect(find.byType(ColoredBox), findsOneWidget); final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); renderColoredBox.paint(mockContext, Offset.zero); expect(mockCanvas.rects.single, const Rect.fromLTWH(0, 0, 800, 600)); expect(mockCanvas.paints.single.color, colorToPaint); expect(mockContext.children, isEmpty); expect(mockContext.offsets, isEmpty); }); testWidgets('ColoredBox - size, child', (WidgetTester tester) async { const ValueKey<int> key = ValueKey<int>(0); const Widget child = SizedBox.expand(key: key); await tester.pumpWidget(const ColoredBox(color: colorToPaint, child: child)); expect(find.byType(ColoredBox), findsOneWidget); final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox)); final RenderObject renderSizedBox = tester.renderObject(find.byKey(key)); renderColoredBox.paint(mockContext, Offset.zero); expect(mockCanvas.rects.single, const Rect.fromLTWH(0, 0, 800, 600)); expect(mockCanvas.paints.single.color, colorToPaint); expect(mockContext.children.single, renderSizedBox); expect(mockContext.offsets.single, Offset.zero); }); testWidgets('ColoredBox - debugFillProperties', (WidgetTester tester) async { const ColoredBox box = ColoredBox(color: colorToPaint); final DiagnosticPropertiesBuilder properties = DiagnosticPropertiesBuilder(); box.debugFillProperties(properties); expect(properties.properties.first.value, colorToPaint); }); }); testWidgets('Inconsequential golden test', (WidgetTester tester) async { // The test validates the Flutter Gold integration. Any changes to the // golden file can be approved at any time. await tester.pumpWidget(RepaintBoundary( child: Container( color: const Color(0xABCDABCD), ), )); await tester.pumpAndSettle(); await expectLater( find.byType(RepaintBoundary), matchesGoldenFile('inconsequential_golden_file.png'), ); }); testWidgets('IgnorePointer ignores pointers', (WidgetTester tester) async { final List<String> logs = <String>[]; Widget target({required bool ignoring}) => Align( alignment: Alignment.topLeft, child: Directionality( textDirection: TextDirection.ltr, child: SizedBox( width: 100, height: 100, child: Listener( onPointerDown: (_) { logs.add('down1'); }, child: MouseRegion( onEnter: (_) { logs.add('enter1'); }, onExit: (_) { logs.add('exit1'); }, cursor: SystemMouseCursors.forbidden, child: Stack( children: <Widget>[ Listener( onPointerDown: (_) { logs.add('down2'); }, child: MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) { logs.add('enter2'); }, onExit: (_) { logs.add('exit2'); }, ), ), IgnorePointer( ignoring: ignoring, child: Listener( onPointerDown: (_) { logs.add('down3'); }, child: MouseRegion( cursor: SystemMouseCursors.text, onEnter: (_) { logs.add('enter3'); }, onExit: (_) { logs.add('exit3'); }, ), ), ), ], ), ), ), ), ), ); final TestGesture gesture = await tester.createGesture(pointer: 1, kind: PointerDeviceKind.mouse); await gesture.addPointer(location: const Offset(200, 200)); addTearDown(gesture.removePointer); await tester.pumpWidget(target(ignoring: true)); expect(logs, isEmpty); await gesture.moveTo(const Offset(50, 50)); expect(logs, <String>['enter1', 'enter2']); logs.clear(); await gesture.down(const Offset(50, 50)); expect(logs, <String>['down2', 'down1']); logs.clear(); await gesture.up(); expect(logs, isEmpty); await tester.pumpWidget(target(ignoring: false)); expect(logs, <String>['exit2', 'enter3']); logs.clear(); await gesture.down(const Offset(50, 50)); expect(logs, <String>['down3', 'down1']); logs.clear(); await gesture.up(); expect(logs, isEmpty); await tester.pumpWidget(target(ignoring: true)); expect(logs, <String>['exit3', 'enter2']); logs.clear(); }); testWidgets('AbsorbPointer absorbs pointers', (WidgetTester tester) async { final List<String> logs = <String>[]; Widget target({required bool absorbing}) => Align( alignment: Alignment.topLeft, child: Directionality( textDirection: TextDirection.ltr, child: SizedBox( width: 100, height: 100, child: Listener( onPointerDown: (_) { logs.add('down1'); }, child: MouseRegion( onEnter: (_) { logs.add('enter1'); }, onExit: (_) { logs.add('exit1'); }, cursor: SystemMouseCursors.forbidden, child: Stack( children: <Widget>[ Listener( onPointerDown: (_) { logs.add('down2'); }, child: MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) { logs.add('enter2'); }, onExit: (_) { logs.add('exit2'); }, ), ), AbsorbPointer( absorbing: absorbing, child: Listener( onPointerDown: (_) { logs.add('down3'); }, child: MouseRegion( cursor: SystemMouseCursors.text, onEnter: (_) { logs.add('enter3'); }, onExit: (_) { logs.add('exit3'); }, ), ), ), ], ), ), ), ), ), ); final TestGesture gesture = await tester.createGesture(pointer: 1, kind: PointerDeviceKind.mouse); await gesture.addPointer(location: const Offset(200, 200)); addTearDown(gesture.removePointer); await tester.pumpWidget(target(absorbing: true)); expect(logs, isEmpty); await gesture.moveTo(const Offset(50, 50)); expect(logs, <String>['enter1']); logs.clear(); await gesture.down(const Offset(50, 50)); expect(logs, <String>['down1']); logs.clear(); await gesture.up(); expect(logs, isEmpty); await tester.pumpWidget(target(absorbing: false)); expect(logs, <String>['enter3']); logs.clear(); await gesture.down(const Offset(50, 50)); expect(logs, <String>['down3', 'down1']); logs.clear(); await gesture.up(); expect(logs, isEmpty); await tester.pumpWidget(target(absorbing: true)); expect(logs, <String>['exit3']); logs.clear(); }); testWidgets('Wrap implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); Wrap( spacing: 8.0, // gap between adjacent Text widget runSpacing: 4.0, // gap between lines textDirection: TextDirection.ltr, verticalDirection: VerticalDirection.up, children: const <Widget>[ Text('Hamilton'), Text('Lafayette'), Text('Mulligan'), ], ).debugFillProperties(builder); final List<String> description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, unorderedMatches(<dynamic>[ contains('direction: horizontal'), contains('alignment: start'), contains('spacing: 8.0'), contains('runAlignment: start'), contains('runSpacing: 4.0'), contains('crossAxisAlignment: start'), contains('textDirection: ltr'), contains('verticalDirection: up'), ])); }); } HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox); class HitsRenderBox extends Matcher { const HitsRenderBox(this.renderBox); final RenderBox renderBox; @override Description describe(Description description) => description.add('hit test result contains ').addDescriptionOf(renderBox); @override bool matches(dynamic item, Map<dynamic, dynamic> matchState) { final HitTestResult hitTestResult = item as HitTestResult; return hitTestResult.path.where( (HitTestEntry entry) => entry.target == renderBox, ).isNotEmpty; } } DoesNotHitRenderBox doesNotHit(RenderBox renderBox) => DoesNotHitRenderBox(renderBox); class DoesNotHitRenderBox extends Matcher { const DoesNotHitRenderBox(this.renderBox); final RenderBox renderBox; @override Description describe(Description description) => description.add("hit test result doesn't contain ").addDescriptionOf(renderBox); @override bool matches(dynamic item, Map<dynamic, dynamic> matchState) { final HitTestResult hitTestResult = item as HitTestResult; return hitTestResult.path.where( (HitTestEntry entry) => entry.target == renderBox, ).isEmpty; } } class _MockPaintingContext extends Fake implements PaintingContext { final List<RenderObject> children = <RenderObject>[]; final List<Offset> offsets = <Offset>[]; @override final _MockCanvas canvas = _MockCanvas(); @override void paintChild(RenderObject child, Offset offset) { children.add(child); offsets.add(offset); } } class _MockCanvas extends Fake implements Canvas { final List<Rect> rects = <Rect>[]; final List<Paint> paints = <Paint>[]; bool didPaint = false; @override void drawRect(Rect rect, Paint paint) { rects.add(rect); paints.add(paint); } }