flutter/packages/flutter/test/material/scaffold_test.dart
amirh c39f2f26f7
Move the notch computation from the FAB to the BAB. (#18372)
Move the notch computation from the FAB to the BAB.

The notch in the BAB (bottom action bar) for the FAB (floating action button) was previously kept as part of the FAB's implementation. This was done to keep the shape of the FAB and the shape of the notch coupled.
That approach resulted in a somewhat complex and 'non Fluttery' mechanism for propagating the notch computation from the FAB to the BAB.

This CL uncouples the FAB and the notch computation.
With the new API the BAB computes its overall shape including the notch using a NotchedShape delegate.

This includes multiple breaking changes:
  * Scaffold.setFloatingActionButtonNotchFor is deleted.
  * The ComputeNotch type is deleted.
  * The hasNotch property of BottomAppBar is deleted.
  * The notchMargin property of FloatingActionButton is deleted.

Quick migration guide from the previous API:

| Previous API | New API |
| ------------------|-------------|
| BottomAppBar(hasNotch: false) | BottomAppBar() |
| Using a FloatingActionButton with: BottomAppBar() / BottomAppBar(hasNotch: true) | BottomAppBar(shape: CircularNotchedRectangle()) |
| Scaffold.setFloatingActionButtonNotchFor(..) | No longer supported |
2018-06-21 15:51:21 -07:00

1065 lines
38 KiB
Dart

// Copyright 2015 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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('Scaffold control test', (WidgetTester tester) async {
final Key bodyKey = new UniqueKey();
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new Scaffold(
appBar: new AppBar(title: const Text('Title')),
body: new Container(key: bodyKey),
),
));
expect(tester.takeException(), isFlutterError);
await tester.pumpWidget(new MaterialApp(
home: new Scaffold(
appBar: new AppBar(title: const Text('Title')),
body: new Container(key: bodyKey),
),
));
RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey));
expect(bodyBox.size, equals(const Size(800.0, 544.0)));
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(viewInsets: const EdgeInsets.only(bottom: 100.0)),
child: new Scaffold(
appBar: new AppBar(title: const Text('Title')),
body: new Container(key: bodyKey),
),
),
));
bodyBox = tester.renderObject(find.byKey(bodyKey));
expect(bodyBox.size, equals(const Size(800.0, 444.0)));
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(viewInsets: const EdgeInsets.only(bottom: 100.0)),
child: new Scaffold(
appBar: new AppBar(title: const Text('Title')),
body: new Container(key: bodyKey),
resizeToAvoidBottomPadding: false,
),
),
));
bodyBox = tester.renderObject(find.byKey(bodyKey));
expect(bodyBox.size, equals(const Size(800.0, 544.0)));
});
testWidgets('Scaffold large bottom padding test', (WidgetTester tester) async {
final Key bodyKey = new UniqueKey();
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(
viewInsets: const EdgeInsets.only(bottom: 700.0),
),
child: new Scaffold(
body: new Container(key: bodyKey),
),
),
));
final RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey));
expect(bodyBox.size, equals(const Size(800.0, 0.0)));
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(
viewInsets: const EdgeInsets.only(bottom: 500.0),
),
child: new Scaffold(
body: new Container(key: bodyKey),
),
),
));
expect(bodyBox.size, equals(const Size(800.0, 100.0)));
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(
viewInsets: const EdgeInsets.only(bottom: 580.0),
),
child: new Scaffold(
appBar: new AppBar(
title: const Text('Title'),
),
body: new Container(key: bodyKey),
),
),
));
expect(bodyBox.size, equals(const Size(800.0, 0.0)));
});
testWidgets('Floating action entrance/exit animation', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp(home: const Scaffold(
floatingActionButton: const FloatingActionButton(
key: const Key('one'),
onPressed: null,
child: const Text('1'),
),
)));
expect(tester.binding.transientCallbackCount, 0);
await tester.pumpWidget(new MaterialApp(home: const Scaffold(
floatingActionButton: const FloatingActionButton(
key: const Key('two'),
onPressed: null,
child: const Text('2'),
),
)));
expect(tester.binding.transientCallbackCount, greaterThan(0));
await tester.pumpWidget(new Container());
expect(tester.binding.transientCallbackCount, 0);
await tester.pumpWidget(new MaterialApp(home: const Scaffold()));
expect(tester.binding.transientCallbackCount, 0);
await tester.pumpWidget(new MaterialApp(home: const Scaffold(
floatingActionButton: const FloatingActionButton(
key: const Key('one'),
onPressed: null,
child: const Text('1'),
),
)));
expect(tester.binding.transientCallbackCount, greaterThan(0));
});
testWidgets('Floating action button directionality', (WidgetTester tester) async {
Widget build(TextDirection textDirection) {
return new Directionality(
textDirection: textDirection,
child: const MediaQuery(
data: const MediaQueryData(
viewInsets: const EdgeInsets.only(bottom: 200.0),
),
child: const Scaffold(
floatingActionButton: const FloatingActionButton(
onPressed: null,
child: const Text('1'),
),
),
),
);
}
await tester.pumpWidget(build(TextDirection.ltr));
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0));
await tester.pumpWidget(build(TextDirection.rtl));
expect(tester.binding.transientCallbackCount, 0);
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 356.0));
});
testWidgets('Drawer scrolling', (WidgetTester tester) async {
final Key drawerKey = new UniqueKey();
const double appBarHeight = 256.0;
final ScrollController scrollOffset = new ScrollController();
await tester.pumpWidget(
new MaterialApp(
home: new Scaffold(
drawer: new Drawer(
key: drawerKey,
child: new ListView(
controller: scrollOffset,
children: new List<Widget>.generate(10,
(int index) => new SizedBox(height: 100.0, child: new Text('D$index'))
)
)
),
body: new CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: appBarHeight,
title: const Text('Title'),
flexibleSpace: const FlexibleSpaceBar(title: const Text('Title')),
),
new SliverPadding(
padding: const EdgeInsets.only(top: appBarHeight),
sliver: new SliverList(
delegate: new SliverChildListDelegate(new List<Widget>.generate(
10, (int index) => new SizedBox(height: 100.0, child: new Text('B$index')),
)),
),
),
],
),
)
)
);
final ScaffoldState state = tester.firstState(find.byType(Scaffold));
state.openDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(scrollOffset.offset, 0.0);
const double scrollDelta = 80.0;
await tester.drag(find.byKey(drawerKey), const Offset(0.0, -scrollDelta));
await tester.pump();
expect(scrollOffset.offset, scrollDelta);
final RenderBox renderBox = tester.renderObject(find.byType(AppBar));
expect(renderBox.size.height, equals(appBarHeight));
});
Widget _buildStatusBarTestApp(TargetPlatform platform) {
return new MaterialApp(
theme: new ThemeData(platform: platform),
home: new MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.only(top: 25.0)), // status bar
child: new Scaffold(
body: new CustomScrollView(
primary: true,
slivers: <Widget>[
const SliverAppBar(
title: const Text('Title')
),
new SliverList(
delegate: new SliverChildListDelegate(new List<Widget>.generate(
20, (int index) => new SizedBox(height: 100.0, child: new Text('$index')),
)),
),
],
),
),
),
);
}
testWidgets('Tapping the status bar scrolls to top on iOS', (WidgetTester tester) async {
await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.iOS));
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(500.0);
expect(scrollable.position.pixels, equals(500.0));
await tester.tapAt(const Offset(100.0, 10.0));
await tester.pumpAndSettle();
expect(scrollable.position.pixels, equals(0.0));
});
testWidgets('Tapping the status bar does not scroll to top on Android', (WidgetTester tester) async {
await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.android));
final ScrollableState scrollable = tester.state(find.byType(Scrollable));
scrollable.position.jumpTo(500.0);
expect(scrollable.position.pixels, equals(500.0));
await tester.tapAt(const Offset(100.0, 10.0));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(scrollable.position.pixels, equals(500.0));
});
testWidgets('Bottom sheet cannot overlap app bar', (WidgetTester tester) async {
final Key sheetKey = new UniqueKey();
await tester.pumpWidget(
new MaterialApp(
theme: new ThemeData(platform: TargetPlatform.android),
home: new Scaffold(
appBar: new AppBar(
title: const Text('Title'),
),
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
Scaffold.of(context).showBottomSheet<Null>((BuildContext context) {
return new Container(
key: sheetKey,
color: Colors.blue[500],
);
});
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1));
final RenderBox appBarBox = tester.renderObject(find.byType(AppBar));
final RenderBox sheetBox = tester.renderObject(find.byKey(sheetKey));
final Offset appBarBottomRight = appBarBox.localToGlobal(appBarBox.size.bottomRight(Offset.zero));
final Offset sheetTopRight = sheetBox.localToGlobal(sheetBox.size.topRight(Offset.zero));
expect(appBarBottomRight, equals(sheetTopRight));
});
testWidgets('Persistent bottom buttons are persistent', (WidgetTester tester) async {
bool didPressButton = false;
await tester.pumpWidget(
new MaterialApp(
home: new Scaffold(
body: new SingleChildScrollView(
child: new Container(
color: Colors.amber[500],
height: 5000.0,
child: const Text('body'),
),
),
persistentFooterButtons: <Widget>[
new FlatButton(
onPressed: () {
didPressButton = true;
},
child: const Text('X'),
)
],
),
),
);
await tester.drag(find.text('body'), const Offset(0.0, -1000.0));
expect(didPressButton, isFalse);
await tester.tap(find.text('X'));
expect(didPressButton, isTrue);
});
testWidgets('Persistent bottom buttons apply media padding', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(
padding: const EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0),
),
child: new Scaffold(
body: new SingleChildScrollView(
child: new Container(
color: Colors.amber[500],
height: 5000.0,
child: const Text('body'),
),
),
persistentFooterButtons: const <Widget>[const Placeholder()],
),
),
),
);
expect(tester.getBottomLeft(find.byType(ButtonBar)), const Offset(10.0, 560.0));
expect(tester.getBottomRight(find.byType(ButtonBar)), const Offset(770.0, 560.0));
});
group('back arrow', () {
Future<Null> expectBackIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon) async {
final GlobalKey rootKey = new GlobalKey();
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (_) => new Container(key: rootKey, child: const Text('Home')),
'/scaffold': (_) => new Scaffold(
appBar: new AppBar(),
body: const Text('Scaffold'),
)
};
await tester.pumpWidget(
new MaterialApp(theme: new ThemeData(platform: platform), routes: routes)
);
Navigator.pushNamed(rootKey.currentContext, '/scaffold');
await tester.pump();
await tester.pump(const Duration(seconds: 1));
final Icon icon = tester.widget(find.byType(Icon));
expect(icon.icon, expectedIcon);
}
testWidgets('Back arrow uses correct default on Android', (WidgetTester tester) async {
await expectBackIcon(tester, TargetPlatform.android, Icons.arrow_back);
});
testWidgets('Back arrow uses correct default on Fuchsia', (WidgetTester tester) async {
await expectBackIcon(tester, TargetPlatform.fuchsia, Icons.arrow_back);
});
testWidgets('Back arrow uses correct default on iOS', (WidgetTester tester) async {
await expectBackIcon(tester, TargetPlatform.iOS, Icons.arrow_back_ios);
});
});
group('close button', () {
Future<Null> expectCloseIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon, PageRoute<void> routeBuilder()) async {
await tester.pumpWidget(
new MaterialApp(
theme: new ThemeData(platform: platform),
home: new Scaffold(appBar: new AppBar(), body: const Text('Page 1')),
)
);
tester.state<NavigatorState>(find.byType(Navigator)).push(routeBuilder());
await tester.pump();
await tester.pump(const Duration(seconds: 1));
final Icon icon = tester.widget(find.byType(Icon));
expect(icon.icon, expectedIcon);
expect(find.byType(CloseButton), findsOneWidget);
}
PageRoute<void> materialRouteBuilder() {
return new MaterialPageRoute<void>(
builder: (BuildContext context) {
return new Scaffold(appBar: new AppBar(), body: const Text('Page 2'));
},
fullscreenDialog: true,
);
}
PageRoute<void> customPageRouteBuilder() {
return new _CustomPageRoute<void>(
builder: (BuildContext context) {
return new Scaffold(appBar: new AppBar(), body: const Text('Page 2'));
},
fullscreenDialog: true,
);
}
testWidgets('Close button shows correctly on Android', (WidgetTester tester) async {
await expectCloseIcon(tester, TargetPlatform.android, Icons.close, materialRouteBuilder);
});
testWidgets('Close button shows correctly on Fuchsia', (WidgetTester tester) async {
await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, materialRouteBuilder);
});
testWidgets('Close button shows correctly on iOS', (WidgetTester tester) async {
await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, materialRouteBuilder);
});
testWidgets('Close button shows correctly with custom page route on Android', (WidgetTester tester) async {
await expectCloseIcon(tester, TargetPlatform.android, Icons.close, customPageRouteBuilder);
});
testWidgets('Close button shows correctly with custom page route on Fuchsia', (WidgetTester tester) async {
await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, customPageRouteBuilder);
});
testWidgets('Close button shows correctly with custom page route on iOS', (WidgetTester tester) async {
await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, customPageRouteBuilder);
});
});
group('body size', () {
testWidgets('body size with container', (WidgetTester tester) async {
final Key testKey = new UniqueKey();
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scaffold(
body: new Container(
key: testKey,
),
),
),
));
expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0));
expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
});
testWidgets('body size with sized container', (WidgetTester tester) async {
final Key testKey = new UniqueKey();
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scaffold(
body: new Container(
key: testKey,
height: 100.0,
),
),
),
));
expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 100.0));
expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
});
testWidgets('body size with centered container', (WidgetTester tester) async {
final Key testKey = new UniqueKey();
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scaffold(
body: new Center(
child: new Container(
key: testKey,
),
),
),
),
));
expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0));
expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
});
testWidgets('body size with button', (WidgetTester tester) async {
final Key testKey = new UniqueKey();
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scaffold(
body: new FlatButton(
key: testKey,
onPressed: () { },
child: const Text(''),
),
),
),
));
expect(tester.element(find.byKey(testKey)).size, const Size(88.0, 36.0));
expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
});
});
testWidgets('Open drawer hides underlying semantics tree', (WidgetTester tester) async {
const String bodyLabel = 'I am the body';
const String persistentFooterButtonLabel = 'a button on the bottom';
const String bottomNavigationBarLabel = 'a bar in an app';
const String floatingActionButtonLabel = 'I float in space';
const String drawerLabel = 'I am the reason for this test';
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new MaterialApp(home: const Scaffold(
body: const Text(bodyLabel),
persistentFooterButtons: const <Widget>[const Text(persistentFooterButtonLabel)],
bottomNavigationBar: const Text(bottomNavigationBarLabel),
floatingActionButton: const Text(floatingActionButtonLabel),
drawer: const Drawer(child: const Text(drawerLabel)),
)));
expect(semantics, includesNodeWith(label: bodyLabel));
expect(semantics, includesNodeWith(label: persistentFooterButtonLabel));
expect(semantics, includesNodeWith(label: bottomNavigationBarLabel));
expect(semantics, includesNodeWith(label: floatingActionButtonLabel));
expect(semantics, isNot(includesNodeWith(label: drawerLabel)));
final ScaffoldState state = tester.firstState(find.byType(Scaffold));
state.openDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, isNot(includesNodeWith(label: persistentFooterButtonLabel)));
expect(semantics, isNot(includesNodeWith(label: bottomNavigationBarLabel)));
expect(semantics, isNot(includesNodeWith(label: floatingActionButtonLabel)));
expect(semantics, includesNodeWith(label: drawerLabel));
semantics.dispose();
});
testWidgets('Scaffold and extreme window padding', (WidgetTester tester) async {
final Key appBar = new UniqueKey();
final Key body = new UniqueKey();
final Key floatingActionButton = new UniqueKey();
final Key persistentFooterButton = new UniqueKey();
final Key drawer = new UniqueKey();
final Key bottomNavigationBar = new UniqueKey();
final Key insideAppBar = new UniqueKey();
final Key insideBody = new UniqueKey();
final Key insideFloatingActionButton = new UniqueKey();
final Key insidePersistentFooterButton = new UniqueKey();
final Key insideDrawer = new UniqueKey();
final Key insideBottomNavigationBar = new UniqueKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new MediaQuery(
data: const MediaQueryData(
padding: const EdgeInsets.only(
left: 20.0,
top: 30.0,
right: 50.0,
bottom: 60.0,
),
viewInsets: const EdgeInsets.only(bottom: 200.0),
),
child: new Scaffold(
appBar: new PreferredSize(
preferredSize: const Size(11.0, 13.0),
child: new Container(
key: appBar,
child: new SafeArea(
child: new Placeholder(key: insideAppBar),
),
),
),
body: new Container(
key: body,
child: new SafeArea(
child: new Placeholder(key: insideBody),
),
),
floatingActionButton: new SizedBox(
key: floatingActionButton,
width: 77.0,
height: 77.0,
child: new SafeArea(
child: new Placeholder(key: insideFloatingActionButton),
),
),
persistentFooterButtons: <Widget>[
new SizedBox(
key: persistentFooterButton,
width: 100.0,
height: 90.0,
child: new SafeArea(
child: new Placeholder(key: insidePersistentFooterButton),
),
),
],
drawer: new Container(
key: drawer,
width: 204.0,
child: new SafeArea(
child: new Placeholder(key: insideDrawer),
),
),
bottomNavigationBar: new SizedBox(
key: bottomNavigationBar,
height: 85.0,
child: new SafeArea(
child: new Placeholder(key: insideBottomNavigationBar),
),
),
),
),
),
);
// open drawer
await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(tester.getRect(find.byKey(appBar)), new Rect.fromLTRB(0.0, 0.0, 800.0, 43.0));
expect(tester.getRect(find.byKey(body)), new Rect.fromLTRB(0.0, 43.0, 800.0, 348.0));
expect(tester.getRect(find.byKey(floatingActionButton)), new Rect.fromLTRB(36.0, 255.0, 113.0, 332.0));
expect(tester.getRect(find.byKey(persistentFooterButton)), new Rect.fromLTRB(28.0, 357.0, 128.0, 447.0)); // Note: has 8px each top/bottom padding.
expect(tester.getRect(find.byKey(drawer)), new Rect.fromLTRB(596.0, 0.0, 800.0, 600.0));
expect(tester.getRect(find.byKey(bottomNavigationBar)), new Rect.fromLTRB(0.0, 515.0, 800.0, 600.0));
expect(tester.getRect(find.byKey(insideAppBar)), new Rect.fromLTRB(20.0, 30.0, 750.0, 43.0));
expect(tester.getRect(find.byKey(insideBody)), new Rect.fromLTRB(20.0, 43.0, 750.0, 348.0));
expect(tester.getRect(find.byKey(insideFloatingActionButton)), new Rect.fromLTRB(36.0, 255.0, 113.0, 332.0));
expect(tester.getRect(find.byKey(insidePersistentFooterButton)), new Rect.fromLTRB(28.0, 357.0, 128.0, 447.0));
expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 540.0));
expect(tester.getRect(find.byKey(insideBottomNavigationBar)), new Rect.fromLTRB(20.0, 515.0, 750.0, 540.0));
});
testWidgets('Scaffold and extreme window padding - persistent footer buttons only', (WidgetTester tester) async {
final Key appBar = new UniqueKey();
final Key body = new UniqueKey();
final Key floatingActionButton = new UniqueKey();
final Key persistentFooterButton = new UniqueKey();
final Key drawer = new UniqueKey();
final Key insideAppBar = new UniqueKey();
final Key insideBody = new UniqueKey();
final Key insideFloatingActionButton = new UniqueKey();
final Key insidePersistentFooterButton = new UniqueKey();
final Key insideDrawer = new UniqueKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new MediaQuery(
data: const MediaQueryData(
padding: const EdgeInsets.only(
left: 20.0,
top: 30.0,
right: 50.0,
bottom: 60.0,
),
viewInsets: const EdgeInsets.only(bottom: 200.0),
),
child: new Scaffold(
appBar: new PreferredSize(
preferredSize: const Size(11.0, 13.0),
child: new Container(
key: appBar,
child: new SafeArea(
child: new Placeholder(key: insideAppBar),
),
),
),
body: new Container(
key: body,
child: new SafeArea(
child: new Placeholder(key: insideBody),
),
),
floatingActionButton: new SizedBox(
key: floatingActionButton,
width: 77.0,
height: 77.0,
child: new SafeArea(
child: new Placeholder(key: insideFloatingActionButton),
),
),
persistentFooterButtons: <Widget>[
new SizedBox(
key: persistentFooterButton,
width: 100.0,
height: 90.0,
child: new SafeArea(
child: new Placeholder(key: insidePersistentFooterButton),
),
),
],
drawer: new Container(
key: drawer,
width: 204.0,
child: new SafeArea(
child: new Placeholder(key: insideDrawer),
),
),
),
),
),
);
// open drawer
await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(tester.getRect(find.byKey(appBar)), new Rect.fromLTRB(0.0, 0.0, 800.0, 43.0));
expect(tester.getRect(find.byKey(body)), new Rect.fromLTRB(0.0, 43.0, 800.0, 400.0));
expect(tester.getRect(find.byKey(floatingActionButton)), new Rect.fromLTRB(36.0, 307.0, 113.0, 384.0));
expect(tester.getRect(find.byKey(persistentFooterButton)), new Rect.fromLTRB(28.0, 442.0, 128.0, 532.0)); // Note: has 8px each top/bottom padding.
expect(tester.getRect(find.byKey(drawer)), new Rect.fromLTRB(596.0, 0.0, 800.0, 600.0));
expect(tester.getRect(find.byKey(insideAppBar)), new Rect.fromLTRB(20.0, 30.0, 750.0, 43.0));
expect(tester.getRect(find.byKey(insideBody)), new Rect.fromLTRB(20.0, 43.0, 750.0, 400.0));
expect(tester.getRect(find.byKey(insideFloatingActionButton)), new Rect.fromLTRB(36.0, 307.0, 113.0, 384.0));
expect(tester.getRect(find.byKey(insidePersistentFooterButton)), new Rect.fromLTRB(28.0, 442.0, 128.0, 532.0));
expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 540.0));
});
testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async {
const String bodyLabel = 'I am the body';
const String drawerLabel = 'I am the label on start side';
const String endDrawerLabel = 'I am the label on end side';
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new MaterialApp(home: const Scaffold(
body: const Text(bodyLabel),
drawer: const Drawer(child: const Text(drawerLabel)),
endDrawer: const Drawer(child: const Text(endDrawerLabel)),
)));
expect(semantics, includesNodeWith(label: bodyLabel));
expect(semantics, isNot(includesNodeWith(label: drawerLabel)));
expect(semantics, isNot(includesNodeWith(label: endDrawerLabel)));
final ScaffoldState state = tester.firstState(find.byType(Scaffold));
state.openDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, includesNodeWith(label: drawerLabel));
state.openEndDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, includesNodeWith(label: endDrawerLabel));
semantics.dispose();
});
group('ScaffoldGeometry', () {
testWidgets('bottomNavigationBar', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new Container(),
bottomNavigationBar: new ConstrainedBox(
key: key,
constraints: const BoxConstraints.expand(height: 80.0),
child: new _GeometryListener(),
),
)));
final RenderBox navigationBox = tester.renderObject(find.byKey(key));
final RenderBox appBox = tester.renderObject(find.byType(MaterialApp));
final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
final ScaffoldGeometry geometry = listenerState.cache.value;
expect(
geometry.bottomNavigationBarTop,
appBox.size.height - navigationBox.size.height
);
});
testWidgets('no bottomNavigationBar', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new ConstrainedBox(
constraints: const BoxConstraints.expand(height: 80.0),
child: new _GeometryListener(),
),
)));
final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
final ScaffoldGeometry geometry = listenerState.cache.value;
expect(
geometry.bottomNavigationBarTop,
null
);
});
testWidgets('floatingActionButton', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new Container(),
floatingActionButton: new FloatingActionButton(
key: key,
child: new _GeometryListener(),
onPressed: () {},
),
)));
final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key));
final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
final ScaffoldGeometry geometry = listenerState.cache.value;
final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size;
expect(
geometry.floatingActionButtonArea,
fabRect
);
});
testWidgets('no floatingActionButton', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new ConstrainedBox(
constraints: const BoxConstraints.expand(height: 80.0),
child: new _GeometryListener(),
),
)));
final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
final ScaffoldGeometry geometry = listenerState.cache.value;
expect(
geometry.floatingActionButtonArea,
null
);
});
testWidgets('floatingActionButton entrance/exit animation', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new ConstrainedBox(
constraints: const BoxConstraints.expand(height: 80.0),
child: new _GeometryListener(),
),
)));
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new Container(),
floatingActionButton: new FloatingActionButton(
key: key,
child: new _GeometryListener(),
onPressed: () {},
),
)));
final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
await tester.pump(const Duration(milliseconds: 50));
ScaffoldGeometry geometry = listenerState.cache.value;
final Rect transitioningFabRect = geometry.floatingActionButtonArea;
await tester.pump(const Duration(seconds: 3));
geometry = listenerState.cache.value;
final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key));
final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size;
expect(
geometry.floatingActionButtonArea,
fabRect
);
expect(
geometry.floatingActionButtonArea.center,
transitioningFabRect.center
);
expect(
geometry.floatingActionButtonArea.width,
greaterThan(transitioningFabRect.width)
);
expect(
geometry.floatingActionButtonArea.height,
greaterThan(transitioningFabRect.height)
);
});
testWidgets('change notifications', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
int numNotificationsAtLastFrame = 0;
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new ConstrainedBox(
constraints: const BoxConstraints.expand(height: 80.0),
child: new _GeometryListener(),
),
)));
final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener));
expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
numNotificationsAtLastFrame = listenerState.numNotifications;
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
body: new Container(),
floatingActionButton: new FloatingActionButton(
key: key,
child: new _GeometryListener(),
onPressed: () {},
),
)));
expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
numNotificationsAtLastFrame = listenerState.numNotifications;
await tester.pump(const Duration(milliseconds: 50));
expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
numNotificationsAtLastFrame = listenerState.numNotifications;
await tester.pump(const Duration(seconds: 3));
expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame));
numNotificationsAtLastFrame = listenerState.numNotifications;
});
});
}
class _GeometryListener extends StatefulWidget {
@override
_GeometryListenerState createState() => new _GeometryListenerState();
}
class _GeometryListenerState extends State<_GeometryListener> {
@override
Widget build(BuildContext context) {
return new CustomPaint(
painter: cache
);
}
int numNotifications = 0;
ValueListenable<ScaffoldGeometry> geometryListenable;
_GeometryCachePainter cache;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ValueListenable<ScaffoldGeometry> newListenable = Scaffold.geometryOf(context);
if (geometryListenable == newListenable)
return;
if (geometryListenable != null)
geometryListenable.removeListener(onGeometryChanged);
geometryListenable = newListenable;
geometryListenable.addListener(onGeometryChanged);
cache = new _GeometryCachePainter(geometryListenable);
}
void onGeometryChanged() {
numNotifications += 1;
}
}
// The Scaffold.geometryOf() value is only available at paint time.
// To fetch it for the tests we implement this CustomPainter that just
// caches the ScaffoldGeometry value in its paint method.
class _GeometryCachePainter extends CustomPainter {
_GeometryCachePainter(this.geometryListenable) : super(repaint: geometryListenable);
final ValueListenable<ScaffoldGeometry> geometryListenable;
ScaffoldGeometry value;
@override
void paint(Canvas canvas, Size size) {
value = geometryListenable.value;
}
@override
bool shouldRepaint(_GeometryCachePainter oldDelegate) {
return true;
}
}
class _CustomPageRoute<T> extends PageRoute<T> {
_CustomPageRoute({
@required this.builder,
RouteSettings settings = const RouteSettings(),
this.maintainState = true,
bool fullscreenDialog = false,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: fullscreenDialog);
final WidgetBuilder builder;
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Color get barrierColor => null;
@override
String get barrierLabel => null;
@override
final bool maintainState;
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return builder(context);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return child;
}
}