1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/material.dart';
6import 'package:flutter/rendering.dart';
7import 'package:flutter_test/flutter_test.dart';
8
9void main() {
10 group('SliverAppBar - Stretch', () {
11 testWidgets('fills overscroll', (WidgetTester tester) async {
12 const Key anchor = Key('drag');
13 await tester.pumpWidget(
14 MaterialApp(
15 home: CustomScrollView(
16 physics: const BouncingScrollPhysics(),
17 slivers: <Widget>[
18 const SliverAppBar(stretch: true, expandedHeight: 100.0),
19 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
20 SliverToBoxAdapter(child: Container(height: 800)),
21 ],
22 ),
23 ),
24 );
25
26 final RenderSliverScrollingPersistentHeader header = tester.renderObject(
27 find.byType(SliverAppBar),
28 );
29 expect(header.child!.size.height, equals(100.0));
30 await slowDrag(tester, anchor, const Offset(0.0, 100));
31 expect(header.child!.size.height, equals(200.0));
32 });
33
34 testWidgets('fills overscroll after reverse direction input - scrolling header', (
35 WidgetTester tester,
36 ) async {
37 const Key anchor = Key('drag');
38 await tester.pumpWidget(
39 MaterialApp(
40 home: CustomScrollView(
41 physics: const BouncingScrollPhysics(),
42 slivers: <Widget>[
43 const SliverAppBar(title: Text('Test'), stretch: true, expandedHeight: 100.0),
44 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
45 SliverToBoxAdapter(child: Container(height: 800)),
46 ],
47 ),
48 ),
49 );
50
51 final RenderSliverScrollingPersistentHeader header = tester.renderObject(
52 find.byType(SliverAppBar),
53 );
54 expect(header.child!.size.height, equals(100.0));
55 expect(tester.getCenter(find.text('Test')).dy, 28.0);
56 // First scroll the header away
57 final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(anchor)));
58 await gesture.moveBy(const Offset(0.0, -100.0));
59 await tester.pump(const Duration(milliseconds: 10));
60 expect(header.child!.size.height, equals(56.0));
61 expect(tester.getCenter(find.text('Test', skipOffstage: false)).dy, -28.0);
62 // With the same gesture, scroll back and into overscroll
63 await gesture.moveBy(const Offset(0.0, 200.0));
64 await tester.pump(const Duration(milliseconds: 10));
65 // Header should stretch in overscroll
66 expect(header.child!.size.height, equals(200.0));
67 expect(tester.getCenter(find.text('Test')).dy, 28.0);
68 await gesture.up();
69 await tester.pumpAndSettle();
70 });
71
72 testWidgets('fills overscroll after reverse direction input - floating header', (
73 WidgetTester tester,
74 ) async {
75 const Key anchor = Key('drag');
76 await tester.pumpWidget(
77 MaterialApp(
78 home: CustomScrollView(
79 physics: const BouncingScrollPhysics(),
80 slivers: <Widget>[
81 const SliverAppBar(
82 title: Text('Test'),
83 stretch: true,
84 floating: true,
85 expandedHeight: 100.0,
86 ),
87 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
88 SliverToBoxAdapter(child: Container(height: 800)),
89 ],
90 ),
91 ),
92 );
93
94 final RenderSliverFloatingPersistentHeader header = tester.renderObject(
95 find.byType(SliverAppBar),
96 );
97 expect(header.child!.size.height, equals(100.0));
98 expect(tester.getCenter(find.text('Test')).dy, 28.0);
99 // First scroll the header away
100 final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(anchor)));
101 await gesture.moveBy(const Offset(0.0, -100.0));
102 await tester.pump(const Duration(milliseconds: 10));
103 expect(header.child!.size.height, equals(56.0));
104 expect(tester.getCenter(find.text('Test', skipOffstage: false)).dy, -28.0);
105 // With the same gesture, scroll back and into overscroll
106 await gesture.moveBy(const Offset(0.0, 200.0));
107 await tester.pump(const Duration(milliseconds: 10));
108 // Header should stretch in overscroll
109 expect(header.child!.size.height, equals(200.0));
110 expect(tester.getCenter(find.text('Test')).dy, 28.0);
111 await gesture.up();
112 await tester.pumpAndSettle();
113 });
114
115 testWidgets('does not stretch without overscroll physics', (WidgetTester tester) async {
116 const Key anchor = Key('drag');
117 await tester.pumpWidget(
118 MaterialApp(
119 home: CustomScrollView(
120 physics: const ClampingScrollPhysics(),
121 slivers: <Widget>[
122 const SliverAppBar(stretch: true, expandedHeight: 100.0),
123 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
124 SliverToBoxAdapter(child: Container(height: 800)),
125 ],
126 ),
127 ),
128 );
129
130 final RenderSliverScrollingPersistentHeader header = tester.renderObject(
131 find.byType(SliverAppBar),
132 );
133 expect(header.child!.size.height, equals(100.0));
134 await slowDrag(tester, anchor, const Offset(0.0, 100.0));
135 expect(header.child!.size.height, equals(100.0));
136 });
137
138 testWidgets('default trigger offset', (WidgetTester tester) async {
139 bool didTrigger = false;
140 const Key anchor = Key('drag');
141 await tester.pumpWidget(
142 MaterialApp(
143 home: CustomScrollView(
144 physics: const BouncingScrollPhysics(),
145 slivers: <Widget>[
146 SliverAppBar(
147 stretch: true,
148 expandedHeight: 100.0,
149 onStretchTrigger: () async {
150 didTrigger = true;
151 },
152 ),
153 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
154 SliverToBoxAdapter(child: Container(height: 800)),
155 ],
156 ),
157 ),
158 );
159
160 await slowDrag(tester, anchor, const Offset(0.0, 50.0));
161 expect(didTrigger, isFalse);
162 await tester.pumpAndSettle();
163 await slowDrag(tester, anchor, const Offset(0.0, 150.0));
164 expect(didTrigger, isTrue);
165 });
166
167 testWidgets('custom trigger offset', (WidgetTester tester) async {
168 bool didTrigger = false;
169 const Key anchor = Key('drag');
170 await tester.pumpWidget(
171 MaterialApp(
172 home: CustomScrollView(
173 physics: const BouncingScrollPhysics(),
174 slivers: <Widget>[
175 SliverAppBar(
176 stretch: true,
177 expandedHeight: 100.0,
178 stretchTriggerOffset: 150.0,
179 onStretchTrigger: () async {
180 didTrigger = true;
181 },
182 ),
183 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
184 SliverToBoxAdapter(child: Container(height: 800)),
185 ],
186 ),
187 ),
188 );
189
190 await slowDrag(tester, anchor, const Offset(0.0, 100.0));
191 await tester.pumpAndSettle();
192 expect(didTrigger, isFalse);
193 await slowDrag(tester, anchor, const Offset(0.0, 300.0));
194 expect(didTrigger, isTrue);
195 });
196
197 testWidgets('stretch callback not triggered without overscroll physics', (
198 WidgetTester tester,
199 ) async {
200 bool didTrigger = false;
201 const Key anchor = Key('drag');
202 await tester.pumpWidget(
203 MaterialApp(
204 home: CustomScrollView(
205 physics: const ClampingScrollPhysics(),
206 slivers: <Widget>[
207 SliverAppBar(
208 stretch: true,
209 expandedHeight: 100.0,
210 stretchTriggerOffset: 150.0,
211 onStretchTrigger: () async {
212 didTrigger = true;
213 },
214 ),
215 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
216 SliverToBoxAdapter(child: Container(height: 800)),
217 ],
218 ),
219 ),
220 );
221
222 await slowDrag(tester, anchor, const Offset(0.0, 100.0));
223 await tester.pumpAndSettle();
224 expect(didTrigger, isFalse);
225 await slowDrag(tester, anchor, const Offset(0.0, 300.0));
226 expect(didTrigger, isFalse);
227 });
228
229 testWidgets('asserts reasonable trigger offset', (WidgetTester tester) async {
230 expect(() {
231 return MaterialApp(
232 home: CustomScrollView(
233 physics: const ClampingScrollPhysics(),
234 slivers: <Widget>[
235 SliverAppBar(stretch: true, expandedHeight: 100.0, stretchTriggerOffset: -150.0),
236 SliverToBoxAdapter(child: Container(height: 800)),
237 SliverToBoxAdapter(child: Container(height: 800)),
238 ],
239 ),
240 );
241 }, throwsAssertionError);
242 });
243 });
244
245 group('SliverAppBar - Stretch, Pinned', () {
246 testWidgets('fills overscroll', (WidgetTester tester) async {
247 const Key anchor = Key('drag');
248 await tester.pumpWidget(
249 MaterialApp(
250 home: CustomScrollView(
251 physics: const BouncingScrollPhysics(),
252 slivers: <Widget>[
253 const SliverAppBar(pinned: true, stretch: true, expandedHeight: 100.0),
254 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
255 SliverToBoxAdapter(child: Container(height: 800)),
256 ],
257 ),
258 ),
259 );
260 final RenderSliverPinnedPersistentHeader header = tester.renderObject(
261 find.byType(SliverAppBar),
262 );
263 expect(header.child!.size.height, equals(100.0));
264 await slowDrag(tester, anchor, const Offset(0.0, 100));
265 expect(header.child!.size.height, equals(200.0));
266 });
267
268 testWidgets('does not stretch without overscroll physics', (WidgetTester tester) async {
269 const Key anchor = Key('drag');
270 await tester.pumpWidget(
271 MaterialApp(
272 home: CustomScrollView(
273 physics: const ClampingScrollPhysics(),
274 slivers: <Widget>[
275 const SliverAppBar(pinned: true, stretch: true, expandedHeight: 100.0),
276 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
277 SliverToBoxAdapter(child: Container(height: 800)),
278 ],
279 ),
280 ),
281 );
282 final RenderSliverPinnedPersistentHeader header = tester.renderObject(
283 find.byType(SliverAppBar),
284 );
285 expect(header.child!.size.height, equals(100.0));
286 await slowDrag(tester, anchor, const Offset(0.0, 100));
287 expect(header.child!.size.height, equals(100.0));
288 });
289 });
290
291 group('SliverAppBar - Stretch, Floating', () {
292 testWidgets('fills overscroll', (WidgetTester tester) async {
293 const Key anchor = Key('drag');
294 await tester.pumpWidget(
295 MaterialApp(
296 home: CustomScrollView(
297 physics: const BouncingScrollPhysics(),
298 slivers: <Widget>[
299 const SliverAppBar(floating: true, stretch: true, expandedHeight: 100.0),
300 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
301 SliverToBoxAdapter(child: Container(height: 800)),
302 ],
303 ),
304 ),
305 );
306 final RenderSliverFloatingPersistentHeader header = tester.renderObject(
307 find.byType(SliverAppBar),
308 );
309 expect(header.child!.size.height, equals(100.0));
310 await slowDrag(tester, anchor, const Offset(0.0, 100));
311 expect(header.child!.size.height, equals(200.0));
312 });
313
314 testWidgets('does not fill overscroll without proper physics', (WidgetTester tester) async {
315 const Key anchor = Key('drag');
316 await tester.pumpWidget(
317 MaterialApp(
318 home: CustomScrollView(
319 physics: const ClampingScrollPhysics(),
320 slivers: <Widget>[
321 const SliverAppBar(floating: true, stretch: true, expandedHeight: 100.0),
322 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
323 SliverToBoxAdapter(child: Container(height: 800)),
324 ],
325 ),
326 ),
327 );
328 final RenderSliverFloatingPersistentHeader header = tester.renderObject(
329 find.byType(SliverAppBar),
330 );
331 expect(header.child!.size.height, equals(100.0));
332 await slowDrag(tester, anchor, const Offset(0.0, 100));
333 expect(header.child!.size.height, equals(100.0));
334 });
335 });
336
337 group('SliverAppBar - Stretch, Floating, Pinned', () {
338 testWidgets('fills overscroll', (WidgetTester tester) async {
339 const Key anchor = Key('drag');
340 await tester.pumpWidget(
341 MaterialApp(
342 home: CustomScrollView(
343 physics: const BouncingScrollPhysics(),
344 slivers: <Widget>[
345 const SliverAppBar(
346 floating: true,
347 pinned: true,
348 stretch: true,
349 expandedHeight: 100.0,
350 ),
351 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
352 SliverToBoxAdapter(child: Container(height: 800)),
353 ],
354 ),
355 ),
356 );
357 final RenderSliverFloatingPinnedPersistentHeader header = tester.renderObject(
358 find.byType(SliverAppBar),
359 );
360 expect(header.child!.size.height, equals(100.0));
361 await slowDrag(tester, anchor, const Offset(0.0, 100));
362 expect(header.child!.size.height, equals(200.0));
363 });
364
365 testWidgets('does not fill overscroll without proper physics', (WidgetTester tester) async {
366 const Key anchor = Key('drag');
367 await tester.pumpWidget(
368 MaterialApp(
369 home: CustomScrollView(
370 physics: const ClampingScrollPhysics(),
371 slivers: <Widget>[
372 const SliverAppBar(
373 pinned: true,
374 floating: true,
375 stretch: true,
376 expandedHeight: 100.0,
377 ),
378 SliverToBoxAdapter(child: Container(key: anchor, height: 800)),
379 SliverToBoxAdapter(child: Container(height: 800)),
380 ],
381 ),
382 ),
383 );
384 final RenderSliverFloatingPinnedPersistentHeader header = tester.renderObject(
385 find.byType(SliverAppBar),
386 );
387 expect(header.child!.size.height, equals(100.0));
388 await slowDrag(tester, anchor, const Offset(0.0, 100));
389 expect(header.child!.size.height, equals(100.0));
390 });
391 });
392}
393
394Future<void> slowDrag(WidgetTester tester, Key widget, Offset offset) async {
395 final Offset target = tester.getCenter(find.byKey(widget));
396 final TestGesture gesture = await tester.startGesture(target);
397 await gesture.moveBy(offset);
398 await tester.pump(const Duration(milliseconds: 10));
399 await gesture.up();
400}
401