// Copyright 2018 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 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; void main() { testWidgets('no overlap with floating action button', (WidgetTester tester) async { await tester.pumpWidget( new MaterialApp( home: const Scaffold( floatingActionButton: const FloatingActionButton( onPressed: null, ), bottomNavigationBar: const ShapeListener(const BottomAppBar()), ), ), ); final ShapeListenerState shapeListenerState = tester.state(find.byType(ShapeListener)); final RenderBox renderBox = tester.renderObject(find.byType(BottomAppBar)); final Path expectedPath = new Path() ..addRect(Offset.zero & renderBox.size); final Path actualPath = shapeListenerState.cache.value; expect( actualPath, coversSameAreaAs( expectedPath, areaToCompare: (Offset.zero & renderBox.size).inflate(5.0), ) ); }); testWidgets('color defaults to Theme.bottomAppBarColor', (WidgetTester tester) async { await tester.pumpWidget( new MaterialApp( home: new Builder( builder: (BuildContext context) { return new Theme( data: Theme.of(context).copyWith(bottomAppBarColor: const Color(0xffffff00)), child: const Scaffold( floatingActionButton: const FloatingActionButton( onPressed: null, ), bottomNavigationBar: const BottomAppBar(), ), ); } ), ), ); final PhysicalShape physicalShape = tester.widget(find.byType(PhysicalShape).at(0)); expect(physicalShape.color, const Color(0xffffff00)); }); testWidgets('color overrides theme color', (WidgetTester tester) async { await tester.pumpWidget( new MaterialApp( home: new Builder( builder: (BuildContext context) { return new Theme( data: Theme.of(context).copyWith(bottomAppBarColor: const Color(0xffffff00)), child: const Scaffold( floatingActionButton: const FloatingActionButton( onPressed: null, ), bottomNavigationBar: const BottomAppBar( color: const Color(0xff0000ff) ), ), ); } ), ), ); final PhysicalShape physicalShape = tester.widget(find.byType(PhysicalShape).at(0)); expect(physicalShape.color, const Color(0xff0000ff)); }); // This is a regression test for a bug we had where toggling hasNotch // will crash, as the shouldReclip method of ShapeBorderClipper or // _BottomAppBarClipper will try an illegal downcast. testWidgets('toggle hasNotch', (WidgetTester tester) async { await tester.pumpWidget( new MaterialApp( home: const Scaffold( bottomNavigationBar: const BottomAppBar( hasNotch: true, ), ), ), ); await tester.pumpWidget( new MaterialApp( home: const Scaffold( bottomNavigationBar: const BottomAppBar( hasNotch: false, ), ), ), ); await tester.pumpWidget( new MaterialApp( home: const Scaffold( bottomNavigationBar: const BottomAppBar( hasNotch: true, ), ), ), ); }); // TODO(amirh): test a BottomAppBar with hasNotch=false and an overlapping // FAB. // // Cannot test this before https://github.com/flutter/flutter/pull/14368 // as there is no way to make the FAB and BAB overlap. testWidgets('observes safe area', (WidgetTester tester) async { await tester.pumpWidget( new MaterialApp( home: const MediaQuery( data: const MediaQueryData( padding: const EdgeInsets.all(50.0), ), child: const Scaffold( bottomNavigationBar: const BottomAppBar( child: const Center( child: const Text('safe'), ), ), ), ), ), ); expect( tester.getBottomLeft(find.widgetWithText(Center, 'safe')), const Offset(50.0, 550.0), ); }); } // The bottom app bar clip path computation is only available at paint time. // In order to examine the notch path we implement this caching painter which // at paint time looks for for a descendant PhysicalShape and caches the // clip path it is using. class ClipCachePainter extends CustomPainter { ClipCachePainter(this.context); Path value; BuildContext context; @override void paint(Canvas canvas, Size size) { final RenderPhysicalShape physicalShape = findPhysicalShapeChild(context); value = physicalShape.clipper.getClip(size); } RenderPhysicalShape findPhysicalShapeChild(BuildContext context) { RenderPhysicalShape result; context.visitChildElements((Element e) { final RenderObject renderObject = e.findRenderObject(); if (renderObject.runtimeType == RenderPhysicalShape) { assert(result == null); result = renderObject; } else { result = findPhysicalShapeChild(e); } }); return result; } @override bool shouldRepaint(ClipCachePainter oldDelegate) { return true; } } class ShapeListener extends StatefulWidget { const ShapeListener(this.child); final Widget child; @override State createState() => new ShapeListenerState(); } class ShapeListenerState extends State<ShapeListener> { @override Widget build(BuildContext context) { return new CustomPaint( child: widget.child, painter: cache ); } ClipCachePainter cache; @override void didChangeDependencies() { super.didChangeDependencies(); cache = new ClipCachePainter(context); } }