// 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. import 'dart:async'; import 'dart:math' as math; import 'dart:ui' as ui show Image; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../image_data.dart'; import '../rendering/mock_canvas.dart'; class TestImageProvider extends ImageProvider<TestImageProvider> { TestImageProvider(this.future); final Future<void> future; static late ui.Image image; @override Future<TestImageProvider> obtainKey(ImageConfiguration configuration) { return SynchronousFuture<TestImageProvider>(this); } @override ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) { return OneFrameImageStreamCompleter( future.then<ImageInfo>((void value) => ImageInfo(image: image)), ); } } Future<void> main() async { AutomatedTestWidgetsFlutterBinding(); TestImageProvider.image = await decodeImageFromList(Uint8List.fromList(kTransparentImage)); testWidgets('DecoratedBox handles loading images', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); final Completer<void> completer = Completer<void>(); await tester.pumpWidget( KeyedSubtree( key: key, child: DecoratedBox( decoration: BoxDecoration( image: DecorationImage( image: TestImageProvider(completer.future), ), ), ), ), ); expect(tester.binding.hasScheduledFrame, isFalse); completer.complete(); await tester.idle(); expect(tester.binding.hasScheduledFrame, isTrue); await tester.pump(); expect(tester.binding.hasScheduledFrame, isFalse); }); testWidgets('Moving a DecoratedBox', (WidgetTester tester) async { final Completer<void> completer = Completer<void>(); final Widget subtree = KeyedSubtree( key: GlobalKey(), child: RepaintBoundary( child: DecoratedBox( decoration: BoxDecoration( image: DecorationImage( image: TestImageProvider(completer.future), ), ), ), ), ); await tester.pumpWidget(subtree); await tester.idle(); expect(tester.binding.hasScheduledFrame, isFalse); await tester.pumpWidget(Container(child: subtree)); await tester.idle(); expect(tester.binding.hasScheduledFrame, isFalse); completer.complete(); // schedules microtask, does not run it expect(tester.binding.hasScheduledFrame, isFalse); await tester.idle(); // runs microtask expect(tester.binding.hasScheduledFrame, isTrue); await tester.pump(); await tester.idle(); expect(tester.binding.hasScheduledFrame, isFalse); }); testWidgets('Circles can have uniform borders', (WidgetTester tester) async { await tester.pumpWidget( Container( padding: const EdgeInsets.all(50.0), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(width: 10.0, color: const Color(0x80FF00FF)), color: Colors.teal[600], ), ), ); }); testWidgets('Bordered Container insets its child', (WidgetTester tester) async { const Key key = Key('outerContainer'); await tester.pumpWidget( Center( child: Container( key: key, decoration: BoxDecoration(border: Border.all(width: 10.0)), child: const SizedBox( width: 25.0, height: 25.0, ), ), ), ); expect(tester.getSize(find.byKey(key)), equals(const Size(45.0, 45.0))); }); testWidgets('BoxDecoration paints its border correctly', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/7672 const Key key = Key('Container with BoxDecoration'); Widget buildFrame(Border border) { return Center( child: Container( key: key, width: 100.0, height: 50.0, decoration: BoxDecoration(border: border), ), ); } const Color black = Color(0xFF000000); await tester.pumpWidget(buildFrame(Border.all())); expect(find.byKey(key), paints..rect(color: black, style: PaintingStyle.stroke, strokeWidth: 1.0)); await tester.pumpWidget(buildFrame(Border.all(width: 0.0))); expect(find.byKey(key), paints..rect(color: black, style: PaintingStyle.stroke, strokeWidth: 0.0)); const Color green = Color(0xFF00FF00); const BorderSide greenSide = BorderSide(color: green, width: 10.0); await tester.pumpWidget(buildFrame(const Border(top: greenSide))); expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill)); await tester.pumpWidget(buildFrame(const Border(left: greenSide))); expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill)); await tester.pumpWidget(buildFrame(const Border(right: greenSide))); expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill)); await tester.pumpWidget(buildFrame(const Border(bottom: greenSide))); expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill)); const Color blue = Color(0xFF0000FF); const BorderSide blueSide = BorderSide(color: blue, width: 0.0); await tester.pumpWidget(buildFrame(const Border(top: blueSide, right: greenSide, bottom: greenSide))); expect( find.byKey(key), paints ..path() // There's not much point checking the arguments to these calls because paintBorder ..path() // reuses the same Paint object each time, configured differently, and so they will ..path(), // all appear to have the same settings here (that of the last call). ); }); testWidgets('BoxDecoration paints its border correctly', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/12165 await tester.pumpWidget( Column( children: <Widget>[ Container( // There's not currently a way to verify that this paints the same size as the others, // so the pattern below just asserts that there's four paths but doesn't check the geometry. width: 100.0, height: 100.0, decoration: const BoxDecoration( border: Border( top: BorderSide( width: 10.0, color: Color(0xFFEEEEEE), ), left: BorderSide( width: 10.0, color: Color(0xFFFFFFFF), ), right: BorderSide( width: 10.0, color: Color(0xFFFFFFFF), ), bottom: BorderSide( width: 10.0, color: Color(0xFFFFFFFF), ), ), ), ), Container( width: 100.0, height: 100.0, decoration: BoxDecoration( border: Border.all( width: 10.0, color: const Color(0xFFFFFFFF), ), ), ), Container( width: 100.0, height: 100.0, decoration: BoxDecoration( border: Border.all( width: 10.0, color: const Color(0xFFFFFFFF), ), borderRadius: const BorderRadius.only( topRight: Radius.circular(10.0), ), ), ), Container( width: 100.0, height: 100.0, decoration: BoxDecoration( border: Border.all( width: 10.0, color: const Color(0xFFFFFFFF), ), shape: BoxShape.circle, ), ), ], ), ); expect(find.byType(Column), paints ..path() ..path() ..path() ..path() ..rect(rect: const Rect.fromLTRB(355.0, 105.0, 445.0, 195.0)) ..drrect( outer: RRect.fromLTRBAndCorners( 350.0, 200.0, 450.0, 300.0, topRight: const Radius.circular(10.0), ), inner: RRect.fromLTRBAndCorners(360.0, 210.0, 440.0, 290.0), ) ..circle(x: 400.0, y: 350.0, radius: 45.0), ); }); testWidgets('Can hit test on BoxDecoration', (WidgetTester tester) async { late List<int> itemsTapped; const Key key = Key('Container with BoxDecoration'); Widget buildFrame(Border border) { itemsTapped = <int>[]; return Center( child: GestureDetector( behavior: HitTestBehavior.deferToChild, child: Container( key: key, width: 100.0, height: 50.0, decoration: BoxDecoration(border: border), ), onTap: () { itemsTapped.add(1); }, ), ); } await tester.pumpWidget(buildFrame(Border.all())); expect(itemsTapped, isEmpty); await tester.tap(find.byKey(key)); expect(itemsTapped, <int>[1]); await tester.tapAt(const Offset(350.0, 275.0)); expect(itemsTapped, <int>[1,1]); await tester.tapAt(const Offset(449.0, 324.0)); expect(itemsTapped, <int>[1,1,1]); }); testWidgets('Can hit test on BoxDecoration circle', (WidgetTester tester) async { late List<int> itemsTapped; const Key key = Key('Container with BoxDecoration'); Widget buildFrame(Border border) { itemsTapped = <int>[]; return Center( child: GestureDetector( behavior: HitTestBehavior.deferToChild, child: Container( key: key, width: 100.0, height: 50.0, decoration: BoxDecoration(border: border, shape: BoxShape.circle), ), onTap: () { itemsTapped.add(1); }, ), ); } await tester.pumpWidget(buildFrame(Border.all())); expect(itemsTapped, isEmpty); await tester.tapAt(Offset.zero); expect(itemsTapped, isEmpty); await tester.tapAt(const Offset(350.0, 275.0)); expect(itemsTapped, isEmpty); await tester.tapAt(const Offset(400.0, 300.0)); expect(itemsTapped, <int>[1]); await tester.tap(find.byKey(key)); expect(itemsTapped, <int>[1,1]); }); testWidgets('Can hit test on BoxDecoration border', (WidgetTester tester) async { late List<int> itemsTapped; const Key key = Key('Container with BoxDecoration'); Widget buildFrame(Border border) { itemsTapped = <int>[]; return Center( child: GestureDetector( behavior: HitTestBehavior.deferToChild, child: Container( key: key, width: 100.0, height: 50.0, decoration: BoxDecoration(border: border, borderRadius: const BorderRadius.all(Radius.circular(20.0))), ), onTap: () { itemsTapped.add(1); }, ), ); } await tester.pumpWidget(buildFrame(Border.all())); expect(itemsTapped, isEmpty); await tester.tapAt(Offset.zero); expect(itemsTapped, isEmpty); await tester.tapAt(const Offset(350.0, 275.0)); expect(itemsTapped, isEmpty); await tester.tapAt(const Offset(400.0, 300.0)); expect(itemsTapped, <int>[1]); await tester.tap(find.byKey(key)); expect(itemsTapped, <int>[1,1]); }); testWidgets('BoxDecoration not tap outside rounded angles - Top Left', (WidgetTester tester) async { const double height = 50.0; const double width = 50.0; const double radius = 12.3; late List<int> itemsTapped; const Key key = Key('Container with BoxDecoration'); Widget buildFrame(Border border) { itemsTapped = <int>[]; return Align( alignment: Alignment.topLeft, child: GestureDetector( behavior: HitTestBehavior.deferToChild, child: Container( key: key, width: width, height: height, decoration: BoxDecoration(border: border,borderRadius: BorderRadius.circular(radius)), ), onTap: () { itemsTapped.add(1); }, ), ); } await tester.pumpWidget(buildFrame(Border.all())); expect(itemsTapped, isEmpty); // x, y const Offset topLeft = Offset.zero; const Offset borderTopTangent = Offset(radius-1, 0.0); const Offset borderLeftTangent = Offset(0.0,radius-1); //the borderDiagonalOffset is the backslash line //\\######@@@ //#\\###@#### //##\\@###### //##@######## //@########## //@########## const double borderDiagonalOffset = radius - radius * math.sqrt1_2; const Offset fartherBorderRadiusPoint = Offset(borderDiagonalOffset,borderDiagonalOffset); await tester.tapAt(topLeft); expect(itemsTapped, isEmpty, reason: 'top left tapped'); await tester.tapAt(borderTopTangent); expect(itemsTapped, isEmpty, reason: 'border top tapped'); await tester.tapAt(borderLeftTangent); expect(itemsTapped, isEmpty, reason: 'border left tapped'); await tester.tapAt(fartherBorderRadiusPoint); expect(itemsTapped, isEmpty, reason: 'border center tapped'); await tester.tap(find.byKey(key)); expect(itemsTapped, <int>[1]); }); testWidgets('BoxDecoration tap inside rounded angles - Top Left', (WidgetTester tester) async { const double height = 50.0; const double width = 50.0; const double radius = 12.3; late List<int> itemsTapped; const Key key = Key('Container with BoxDecoration'); Widget buildFrame(Border border) { itemsTapped = <int>[]; return Align( alignment: Alignment.topLeft, child: GestureDetector( behavior: HitTestBehavior.deferToChild, child: Container( key: key, width: width, height: height, decoration: BoxDecoration(border: border,borderRadius: BorderRadius.circular(radius)), ), onTap: () { itemsTapped.add(1); }, ), ); } await tester.pumpWidget(buildFrame(Border.all())); expect(itemsTapped, isEmpty); // x, y const Offset borderTopTangent = Offset(radius, 0.0); const Offset borderLeftTangent = Offset(0.0,radius); const double borderDiagonalOffset = radius - radius * math.sqrt1_2; const Offset fartherBorderRadiusPoint = Offset(borderDiagonalOffset+1,borderDiagonalOffset+1); await tester.tapAt(borderTopTangent); expect(itemsTapped, <int>[1], reason: 'border Top not tapped'); await tester.tapAt(borderLeftTangent); expect(itemsTapped, <int>[1,1], reason: 'border Left not tapped'); await tester.tapAt(fartherBorderRadiusPoint); expect(itemsTapped, <int>[1,1,1], reason: 'border center not tapped'); await tester.tap(find.byKey(key)); expect(itemsTapped, <int>[1,1,1,1]); }); testWidgets('BoxDecoration rounded angles other corner works', (WidgetTester tester) async { const double height = 50.0; const double width = 50.0; const double radius = 20; late List<int> itemsTapped; const Key key = Key('Container with BoxDecoration'); Widget buildFrame(Border border) { itemsTapped = <int>[]; return Align( alignment: Alignment.topLeft, child: GestureDetector( behavior: HitTestBehavior.deferToChild, child: Container( key: key, width: width, height: height, decoration: BoxDecoration(border: border,borderRadius: BorderRadius.circular(radius)), ), onTap: () { itemsTapped.add(1); }, ), ); } await tester.pumpWidget(buildFrame(Border.all())); expect(itemsTapped, isEmpty); await tester.tap(find.byKey(key)); expect(itemsTapped, <int>[1]); // x, y const Offset topRightOutside = Offset(width, 0.0); const Offset topRightInside = Offset(width-radius, radius); const Offset bottomRightOutside = Offset(width, height); const Offset bottomRightInside = Offset(width-radius, height-radius); const Offset bottomLeftOutside = Offset(0, height); const Offset bottomLeftInside = Offset(radius, height-radius); const Offset topLeftOutside = Offset.zero; const Offset topLeftInside = Offset(radius, radius); await tester.tapAt(topRightInside); expect(itemsTapped, <int>[1,1], reason: 'top right not tapped'); await tester.tapAt(topRightOutside); expect(itemsTapped, <int>[1,1], reason: 'top right tapped'); await tester.tapAt(bottomRightInside); expect(itemsTapped, <int>[1,1,1], reason: 'bottom right not tapped'); await tester.tapAt(bottomRightOutside); expect(itemsTapped, <int>[1,1,1], reason: 'bottom right tapped'); await tester.tapAt(bottomLeftInside); expect(itemsTapped, <int>[1,1,1,1], reason: 'bottom left not tapped'); await tester.tapAt(bottomLeftOutside); expect(itemsTapped, <int>[1,1,1,1], reason: 'bottom left tapped'); await tester.tapAt(topLeftInside); expect(itemsTapped, <int>[1,1,1,1,1], reason: 'top left not tapped'); await tester.tapAt(topLeftOutside); expect(itemsTapped, <int>[1,1,1,1,1], reason: 'top left tapped'); }); testWidgets("BoxDecoration doesn't crash with BorderRadiusDirectional", (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/88039 await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Container( decoration: BoxDecoration( border: Border.all(), borderRadius: const BorderRadiusDirectional.all( Radius.circular(1.0), ), ), ), ), ); }); }