| // 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 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'semantics_tester.dart'; |
| |
| void main() { |
| testWidgets('Sliver appBars - floating and pinned - correct elevation', (WidgetTester tester) async { |
| await tester.pumpWidget(Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: CustomScrollView( |
| slivers: <Widget>[ |
| const SliverAppBar( |
| bottom: PreferredSize( |
| preferredSize: Size.fromHeight(28), |
| child: Text('Bottom'), |
| ), |
| backgroundColor: Colors.green, |
| floating: true, |
| primary: false, |
| automaticallyImplyLeading: false, |
| ), |
| SliverToBoxAdapter(child: Container(color: Colors.yellow, height: 50.0)), |
| SliverToBoxAdapter(child: Container(color: Colors.red, height: 50.0)), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final RenderPhysicalModel renderObject = tester.renderObject<RenderPhysicalModel>(find.byType(PhysicalModel)); |
| expect(renderObject, isNotNull); |
| expect(renderObject.elevation, 0.0); |
| }); |
| |
| testWidgets('Sliver appbars - floating and pinned - correct semantics', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: CustomScrollView( |
| slivers: <Widget>[ |
| const SliverAppBar( |
| title: Text('Hello'), |
| pinned: true, |
| floating: true, |
| expandedHeight: 200.0, |
| ), |
| SliverFixedExtentList( |
| itemExtent: 100.0, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext _, int index) { |
| return Container( |
| height: 100.0, |
| color: index.isEven ? Colors.red : Colors.yellow, |
| child: Text('Tile $index'), |
| ); |
| }, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final SemanticsTester semantics = SemanticsTester(tester); |
| |
| TestSemantics expectedSemantics = TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics.rootChild( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| thickness: 0, |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'Hello', |
| elevation: 0, |
| flags: <SemanticsFlag>[SemanticsFlag.isHeader, SemanticsFlag.namesRoute], |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| ), |
| TestSemantics( |
| actions: <SemanticsAction>[SemanticsAction.scrollUp], |
| flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling], |
| scrollIndex: 0, |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'Tile 0', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 1', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 2', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 3', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 4', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| TestSemantics( |
| label: 'Tile 5', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| TestSemantics( |
| label: 'Tile 6', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ); |
| expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true)); |
| |
| await tester.fling(find.text('Tile 2'), const Offset(0, -600), 2000); |
| await tester.pumpAndSettle(); |
| |
| expectedSemantics = TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics.rootChild( |
| textDirection: TextDirection.ltr, |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'Hello', |
| flags: <SemanticsFlag>[SemanticsFlag.isHeader, SemanticsFlag.namesRoute], |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| ), |
| TestSemantics( |
| actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown], |
| flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling], |
| scrollIndex: 10, |
| children: <TestSemantics>[ |
| TestSemantics( |
| label: 'Tile 7', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| TestSemantics( |
| label: 'Tile 8', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| TestSemantics( |
| label: 'Tile 9', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| TestSemantics( |
| label: 'Tile 10', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 11', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 12', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 13', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 14', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 15', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 16', |
| textDirection: TextDirection.ltr, |
| ), |
| TestSemantics( |
| label: 'Tile 17', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| TestSemantics( |
| label: 'Tile 18', |
| textDirection: TextDirection.ltr, |
| flags: <SemanticsFlag>[SemanticsFlag.isHidden], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ], |
| ); |
| expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true)); |
| }); |
| |
| testWidgets('Sliver appbars - floating and pinned - second app bar stacks below', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| await tester.pumpWidget( |
| Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: CustomScrollView( |
| controller: controller, |
| slivers: <Widget>[ |
| const SliverAppBar(floating: true, pinned: true, expandedHeight: 200.0, title: Text('A')), |
| const SliverAppBar(primary: false, pinned: true, title: Text('B')), |
| SliverList( |
| delegate: SliverChildListDelegate( |
| const <Widget>[ |
| Text('C'), |
| Text('D'), |
| SizedBox(height: 500.0), |
| Text('E'), |
| SizedBox(height: 500.0), |
| ], |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| const Offset textPositionInAppBar = Offset(16.0, 18.0); |
| expect(tester.getTopLeft(find.text('A')), textPositionInAppBar); |
| // top app bar is 200.0 high at this point |
| expect(tester.getTopLeft(find.text('B')), const Offset(0.0, 200.0) + textPositionInAppBar); |
| // second app bar is 56.0 high |
| expect(tester.getTopLeft(find.text('C')), const Offset(0.0, 200.0 + 56.0)); // height of both appbars |
| final Size cSize = tester.getSize(find.text('C')); |
| controller.jumpTo(200.0 - 56.0); |
| await tester.pump(); |
| expect(tester.getTopLeft(find.text('A')), textPositionInAppBar); |
| // top app bar is now only 56.0 high, same as second |
| expect(tester.getTopLeft(find.text('B')), const Offset(0.0, 56.0) + textPositionInAppBar); |
| expect(tester.getTopLeft(find.text('C')), const Offset(0.0, 56.0 * 2.0)); // height of both collapsed appbars |
| expect(find.text('E'), findsNothing); |
| controller.jumpTo(600.0); |
| await tester.pump(); |
| expect(tester.getTopLeft(find.text('A')), textPositionInAppBar); // app bar is pinned at top |
| expect(tester.getTopLeft(find.text('B')), const Offset(0.0, 56.0) + textPositionInAppBar); // second one too |
| expect(find.text('C'), findsNothing); // contents are scrolled off though |
| expect(find.text('D'), findsNothing); |
| // we have scrolled 600.0 pixels |
| // initial position of E was 200 + 56 + cSize.height + cSize.height + 500 |
| // we've scrolled that up by 600.0, meaning it's at that minus 600 now: |
| expect(tester.getTopLeft(find.text('E')), Offset(0.0, 200.0 + 56.0 + cSize.height * 2.0 + 500.0 - 600.0)); |
| }); |
| |
| testWidgets('Does not crash when there is less than minExtent remainingPaintExtent', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/21887. |
| final ScrollController controller = ScrollController(); |
| const double availableHeight = 50.0; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Center( |
| child: Container( |
| height: availableHeight, |
| color: Colors.green, |
| child: CustomScrollView( |
| controller: controller, |
| slivers: <Widget>[ |
| const SliverAppBar( |
| pinned: true, |
| floating: true, |
| expandedHeight: 120.0, |
| ), |
| SliverList( |
| delegate: SliverChildListDelegate(List<Widget>.generate(20, (int i) { |
| return SizedBox( |
| height: 100.0, |
| child: Text('Tile $i'), |
| ); |
| })), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| final RenderSliverFloatingPinnedPersistentHeader render = tester.renderObject(find.byType(SliverAppBar)); |
| expect(render.minExtent, greaterThan(availableHeight)); // Precondition |
| expect(render.geometry!.scrollExtent, 120.0); |
| expect(render.geometry!.paintExtent, availableHeight); |
| expect(render.geometry!.layoutExtent, availableHeight); |
| |
| controller.jumpTo(200.0); |
| await tester.pumpAndSettle(); |
| expect(render.geometry!.scrollExtent, 120.0); |
| expect(render.geometry!.paintExtent, availableHeight); |
| expect(render.geometry!.layoutExtent, 0.0); |
| }); |
| |
| testWidgets('Pinned and floating SliverAppBar sticks to top the content is scroll down', (WidgetTester tester) async { |
| const Key anchor = Key('drag'); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Center( |
| child: Container( |
| height: 300, |
| color: Colors.green, |
| child: CustomScrollView( |
| physics: const BouncingScrollPhysics(), |
| slivers: <Widget>[ |
| const SliverAppBar( |
| pinned: true, |
| floating: true, |
| expandedHeight: 100.0, |
| ), |
| SliverToBoxAdapter(child: Container(key: anchor, color: Colors.red, height: 100)), |
| SliverToBoxAdapter(child: Container(height: 600, color: Colors.green)), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| final RenderSliverFloatingPinnedPersistentHeader render = tester.renderObject(find.byType(SliverAppBar)); |
| |
| const double scrollDistance = 40; |
| final TestGesture gesture = await tester.press(find.byKey(anchor)); |
| await gesture.moveBy(const Offset(0, scrollDistance)); |
| await tester.pump(); |
| |
| expect(render.geometry!.paintOrigin, -scrollDistance); |
| }); |
| |
| testWidgets('Floating SliverAppBar sticks to top the content is scroll down', (WidgetTester tester) async { |
| const Key anchor = Key('drag'); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Center( |
| child: Container( |
| height: 300, |
| color: Colors.green, |
| child: CustomScrollView( |
| physics: const BouncingScrollPhysics(), |
| slivers: <Widget>[ |
| const SliverAppBar( |
| pinned: false, |
| floating: true, |
| expandedHeight: 100.0, |
| ), |
| SliverToBoxAdapter(child: Container(key: anchor, color: Colors.red, height: 100)), |
| SliverToBoxAdapter(child: Container(height: 600, color: Colors.green)), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| final RenderSliverFloatingPersistentHeader render = tester.renderObject(find.byType(SliverAppBar)); |
| |
| const double scrollDistance = 40; |
| final TestGesture gesture = await tester.press(find.byKey(anchor)); |
| await gesture.moveBy(const Offset(0, scrollDistance)); |
| await tester.pump(); |
| |
| expect(render.geometry!.paintOrigin, -scrollDistance); |
| }); |
| |
| testWidgets('Pinned SliverAppBar sticks to top the content is scroll down', (WidgetTester tester) async { |
| const Key anchor = Key('drag'); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Center( |
| child: Container( |
| height: 300, |
| color: Colors.green, |
| child: CustomScrollView( |
| physics: const BouncingScrollPhysics(), |
| slivers: <Widget>[ |
| const SliverAppBar( |
| pinned: true, |
| floating: false, |
| expandedHeight: 100.0, |
| ), |
| SliverToBoxAdapter(child: Container(key: anchor, color: Colors.red, height: 100)), |
| SliverToBoxAdapter(child: Container(height: 600, color: Colors.green)), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| final RenderSliverPinnedPersistentHeader render = tester.renderObject(find.byType(SliverAppBar)); |
| |
| const double scrollDistance = 40; |
| final TestGesture gesture = await tester.press(find.byKey(anchor)); |
| await gesture.moveBy(const Offset(0, scrollDistance)); |
| await tester.pump(); |
| |
| expect(render.geometry!.paintOrigin, -scrollDistance); |
| }); |
| } |