// 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_test/flutter_test.dart'; class ExpandingBox extends StatefulWidget { const ExpandingBox({this.collapsedSize, this.expandedSize}); final double collapsedSize; final double expandedSize; @override State<ExpandingBox> createState() => _ExpandingBoxState(); } class _ExpandingBoxState extends State<ExpandingBox> with AutomaticKeepAliveClientMixin<ExpandingBox>{ double _height; @override void initState() { super.initState(); _height = widget.collapsedSize; } void toggleSize() { setState(() { _height = _height == widget.collapsedSize ? widget.expandedSize : widget.collapsedSize; }); } @override Widget build(BuildContext context) { super.build(context); return Container( height: _height, color: Colors.green, child: Align( alignment: Alignment.bottomCenter, child: FlatButton( child: const Text('Collapse'), onPressed: toggleSize, ), ), ); } @override bool get wantKeepAlive => true; } void main() { testWidgets('shrink listview', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: ListView.builder( itemBuilder: (BuildContext context, int index) => index == 0 ? const ExpandingBox(collapsedSize: 400, expandedSize: 1200) : Container(height: 300, color: Colors.red), itemCount: 2, ), )); final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; expect(position.activity, isInstanceOf<IdleScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 0.0); await tester.tap(find.byType(FlatButton)); await tester.pump(); final TestGesture drag1 = await tester.startGesture(const Offset(10.0, 500.0)); await tester.pump(); await drag1.moveTo(const Offset(10.0, 0.0)); await tester.pump(); await drag1.up(); await tester.pump(); expect(position.pixels, moreOrLessEquals(500.0)); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 900.0); final TestGesture drag2 = await tester.startGesture(const Offset(10.0, 500.0)); await tester.pump(); await drag2.moveTo(const Offset(10.0, 100.0)); await tester.pump(); await drag2.up(); await tester.pump(); expect(position.maxScrollExtent, 900.0); expect(position.pixels, moreOrLessEquals(900.0)); await tester.pump(); await tester.tap(find.byType(FlatButton)); await tester.pump(); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 100.0); }); testWidgets('shrink listview while dragging', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: ListView.builder( itemBuilder: (BuildContext context, int index) => index == 0 ? const ExpandingBox(collapsedSize: 400, expandedSize: 1200) : Container(height: 300, color: Colors.red), itemCount: 2, ), )); final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; expect(position.activity, isInstanceOf<IdleScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 0.0); await tester.tap(find.byType(FlatButton)); await tester.pump(); // start button animation await tester.pump(const Duration(seconds: 1)); // finish button animation expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 1800.0); expect(position.pixels, 0.0); final TestGesture drag1 = await tester.startGesture(const Offset(10.0, 500.0)); expect(await tester.pumpAndSettle(), 1); // Nothing to animate await drag1.moveTo(const Offset(10.0, 0.0)); expect(await tester.pumpAndSettle(), 1); // Nothing to animate await drag1.up(); expect(await tester.pumpAndSettle(), 1); // Nothing to animate expect(position.pixels, moreOrLessEquals(500.0)); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 900.0); final TestGesture drag2 = await tester.startGesture(const Offset(10.0, 500.0)); expect(await tester.pumpAndSettle(), 1); // Nothing to animate await drag2.moveTo(const Offset(10.0, 100.0)); expect(await tester.pumpAndSettle(), 1); // Nothing to animate expect(position.maxScrollExtent, 900.0); expect(position.pixels, lessThanOrEqualTo(900.0)); expect(position.activity, isInstanceOf<DragScrollActivity>()); final _ExpandingBoxState expandingBoxState = tester.state<_ExpandingBoxState>(find.byType(ExpandingBox)); expandingBoxState.toggleSize(); expect(await tester.pumpAndSettle(), 1); // Nothing to animate expect(position.activity, isInstanceOf<DragScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 100.0); await drag2.moveTo(const Offset(10.0, 150.0)); await drag2.up(); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 50.0); expect(await tester.pumpAndSettle(), 1); // Nothing to animate expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 50.0); }); testWidgets('shrink listview while ballistic', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: GestureDetector( onTap: () { assert(false); }, child: ListView.builder( physics: const RangeMaintainingScrollPhysics(parent: BouncingScrollPhysics()), itemBuilder: (BuildContext context, int index) => index == 0 ? const ExpandingBox(collapsedSize: 400, expandedSize: 1200) : Container(height: 300, color: Colors.red), itemCount: 2, ), ), )); final _ExpandingBoxState expandingBoxState = tester.state<_ExpandingBoxState>(find.byType(ExpandingBox)); expandingBoxState.toggleSize(); final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; expect(position.activity, isInstanceOf<IdleScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 0.0); await tester.pump(); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 1800.0); expect(position.pixels, 0.0); final TestGesture drag1 = await tester.startGesture(const Offset(10.0, 10.0)); await tester.pump(); expect(position.activity, isInstanceOf<HoldScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 1800.0); expect(position.pixels, 0.0); await drag1.moveTo(const Offset(10.0, 50.0)); // to get past the slop and trigger the drag await drag1.moveTo(const Offset(10.0, 550.0)); expect(position.pixels, -500.0); await tester.pump(); expect(position.activity, isInstanceOf<DragScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 1800.0); expect(position.pixels, -500.0); await drag1.up(); await tester.pump(); expect(position.activity, isInstanceOf<BallisticScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 1800.0); expect(position.pixels, -500.0); expandingBoxState.toggleSize(); await tester.pump(); // apply physics without moving clock forward expect(position.activity, isInstanceOf<BallisticScrollActivity>()); // TODO(ianh): Determine why the maxScrollOffset is 200.0 here instead of 100.0 or double.infinity. // expect(position.minScrollExtent, 0.0); // expect(position.maxScrollExtent, 100.0); expect(position.pixels, -500.0); await tester.pumpAndSettle(); // ignoring the exact effects of the animation expect(position.activity, isInstanceOf<IdleScrollActivity>()); expect(position.minScrollExtent, 0.0); expect(position.maxScrollExtent, 100.0); expect(position.pixels, 0.0); }); }