// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/rendering.dart'; void main() { 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, transformHitTests: true, 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), transformHitTests: true, 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), transformHitTests: true, 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); }); }); group('Row', () { testWidgets('multiple baseline aligned children', (WidgetTester tester) async { final UniqueKey key1 = UniqueKey(); final UniqueKey key2 = UniqueKey(); const double fontSize1 = 54; const double fontSize2 = 14; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Container( child: Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: <Widget>[ Text('big text', key: key1, style: const TextStyle(fontSize: fontSize1), ), Text('one\ntwo\nthree\nfour\nfive\nsix\nseven', key: key2, style: const TextStyle(fontSize: fontSize2) ), ], ), ), ), ), ); final RenderBox textBox1 = tester.renderObject(find.byKey(key1)); final RenderBox textBox2 = tester.renderObject(find.byKey(key2)); final RenderBox rowBox = tester.renderObject(find.byType(Row)); // The two Texts are baseline aligned, so some portion of them extends // both above and below the baseline. The first has a huge font size, so // it extends higher above the baseline than usual. The second has many // lines, but being aligned by the first line's baseline, they hang far // below the baseline. The size of the parent row is just enough to // contain both of them. const double ahemBaselineLocation = 0.8; // https://web-platform-tests.org/writing-tests/ahem.html const double aboveBaseline1 = fontSize1 * ahemBaselineLocation; const double belowBaseline1 = fontSize1 * (1 - ahemBaselineLocation); const double aboveBaseline2 = fontSize2 * ahemBaselineLocation; const double belowBaseline2 = fontSize2 * (1 - ahemBaselineLocation) + fontSize2 * 6; final double aboveBaseline = math.max(aboveBaseline1, aboveBaseline2); final double belowBaseline = math.max(belowBaseline1, belowBaseline2); expect(rowBox.size.height, greaterThan(textBox1.size.height)); expect(rowBox.size.height, greaterThan(textBox2.size.height)); expect(rowBox.size.height, closeTo(aboveBaseline + belowBaseline, .001)); expect(tester.getTopLeft(find.byKey(key1)).dy, 0); expect( tester.getTopLeft(find.byKey(key2)).dy, closeTo(aboveBaseline1 - aboveBaseline2, .001), ); }); }); test('UnconstrainedBox toString', () { expect( const UnconstrainedBox(constrainedAxis: Axis.vertical,).toString(), equals('UnconstrainedBox(alignment: center, constrainedAxis: vertical)'), ); expect( const UnconstrainedBox(constrainedAxis: Axis.horizontal, textDirection: TextDirection.rtl, alignment: Alignment.topRight).toString(), equals('UnconstrainedBox(alignment: topRight, constrainedAxis: horizontal, textDirection: rtl)'), ); }); } 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; 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; return hitTestResult.path.where( (HitTestEntry entry) => entry.target == renderBox ).isEmpty; } }