// 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. // @dart = 2.8 import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.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 % 2 == 0 ? 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 Container( child: Text('Tile $i'), height: 100.0, ); })), ), ], ), ), ), ), ); 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); }); }