@@ -13,6 +13,38 @@ import '../flutter_checks.dart';
13
13
import '../model/binding.dart' ;
14
14
import '../model/test_store.dart' ;
15
15
16
+ /// Repeatedly drags `view` by `moveStep` until `finder` is invisible.
17
+ ///
18
+ /// Between each drag, advances the clock by `duration` .
19
+ ///
20
+ /// Throws a [StateError] if `finder` is still visible after `maxIteration`
21
+ /// drags.
22
+ ///
23
+ /// See also:
24
+ /// * [WidgetController.dragUntilVisible] , which does the inverse.
25
+ Future <void > dragUntilInvisible (
26
+ WidgetTester tester,
27
+ FinderBase <Element > finder,
28
+ FinderBase <Element > view,
29
+ Offset moveStep, {
30
+ int maxIteration = 50 ,
31
+ Duration duration = const Duration (milliseconds: 50 ),
32
+ }) {
33
+ return TestAsyncUtils .guard <void >(() async {
34
+ final iteration = maxIteration;
35
+ while (maxIteration > 0 && finder.evaluate ().isNotEmpty) {
36
+ await tester.drag (view, moveStep);
37
+ await tester.pump (duration);
38
+ maxIteration -= 1 ;
39
+ }
40
+ if (maxIteration <= 0 && finder.evaluate ().isNotEmpty) {
41
+ throw StateError (
42
+ 'Finder is still visible after $iteration iterations.'
43
+ ' Consider increasing the number of iterations.' );
44
+ }
45
+ });
46
+ }
47
+
16
48
void main () {
17
49
TestZulipBinding .ensureInitialized ();
18
50
@@ -51,6 +83,15 @@ void main() {
51
83
await tester.pumpAndSettle ();
52
84
}
53
85
86
+ List <StreamMessage > generateStreamMessages ({
87
+ required ZulipStream stream,
88
+ required int count,
89
+ required List <MessageFlag > flags,
90
+ }) {
91
+ return List .generate (count, (index) => eg.streamMessage (
92
+ stream: stream, topic: '${stream .name } topic $index ' , flags: flags));
93
+ }
94
+
54
95
/// Set up an inbox view with lots of interesting content.
55
96
Future <void > setupVarious (WidgetTester tester) async {
56
97
final stream1 = eg.stream (streamId: 1 , name: 'stream 1' );
@@ -61,12 +102,16 @@ void main() {
61
102
await setupPage (tester,
62
103
streams: [stream1, stream2],
63
104
subscriptions: [sub1, sub2],
64
- users: [eg.selfUser, eg.otherUser, eg.thirdUser],
105
+ users: [eg.selfUser, eg.otherUser, eg.thirdUser, eg.fourthUser ],
65
106
unreadMessages: [
66
107
eg.streamMessage (stream: stream1, topic: 'specific topic' , flags: []),
108
+ ...generateStreamMessages (stream: stream1, count: 10 , flags: []),
67
109
eg.streamMessage (stream: stream2, flags: []),
110
+ ...generateStreamMessages (stream: stream2, count: 40 , flags: []),
68
111
eg.dmMessage (from: eg.otherUser, to: [eg.selfUser], flags: []),
69
112
eg.dmMessage (from: eg.otherUser, to: [eg.selfUser, eg.thirdUser], flags: []),
113
+ eg.dmMessage (from: eg.thirdUser, to: [eg.selfUser], flags: []),
114
+ eg.dmMessage (from: eg.fourthUser, to: [eg.selfUser], flags: []),
70
115
]);
71
116
}
72
117
@@ -310,6 +355,28 @@ void main() {
310
355
checkAppearsUncollapsed (tester, findSectionContent);
311
356
});
312
357
358
+ testWidgets ('collapse all-DMs section when partially offscreen: '
359
+ 'header remains sticky at top' , (tester) async {
360
+ await setupVarious (tester);
361
+
362
+ final listFinder = find.byType (Scrollable );
363
+ final dmFinder = find.text (eg.otherUser.fullName).hitTestable ();
364
+
365
+ // Scroll part of [_AllDmsSection] offscreen.
366
+ await dragUntilInvisible (
367
+ tester, dmFinder, listFinder, const Offset (0 , - 50 ));
368
+
369
+ // Check that the header is present (which must therefore
370
+ // be as a sticky header).
371
+ check (findAllDmsHeaderRow (tester)).isNotNull ();
372
+
373
+ await tapCollapseIcon (tester);
374
+
375
+ // Check that the header is still visible even after
376
+ // collapsing the section.
377
+ check (findAllDmsHeaderRow (tester)).isNotNull ();
378
+ });
379
+
313
380
// TODO check it remains collapsed even if you scroll far away and back
314
381
315
382
// TODO check that it's always uncollapsed when it appears after being
@@ -385,6 +452,52 @@ void main() {
385
452
checkAppearsUncollapsed (tester, 1 , findSectionContent);
386
453
});
387
454
455
+ testWidgets ('collapse stream section when partially offscreen: '
456
+ 'header remains sticky at top' , (tester) async {
457
+ await setupVarious (tester);
458
+
459
+ final topicFinder = find.text ('stream 1 topic 4' ).hitTestable ();
460
+ final listFinder = find.byType (Scrollable );
461
+
462
+ // Scroll part of [_StreamSection] offscreen.
463
+ await dragUntilInvisible (
464
+ tester, topicFinder, listFinder, const Offset (0 , - 50 ));
465
+
466
+ // Check that the header is present (which must therefore
467
+ // be as a sticky header).
468
+ check (findStreamHeaderRow (tester, 1 )).isNotNull ();
469
+
470
+ await tapCollapseIcon (tester, 1 );
471
+
472
+ // Check that the header is still visible even after
473
+ // collapsing the section.
474
+ check (findStreamHeaderRow (tester, 1 )).isNotNull ();
475
+ });
476
+
477
+ testWidgets ('collapse stream section in middle of screen: '
478
+ 'header stays fixed' , (tester) async {
479
+ await setupVarious (tester);
480
+
481
+ final headerRow = findStreamHeaderRow (tester, 1 );
482
+ // Check that the header is present.
483
+ check (headerRow).isNotNull ();
484
+
485
+ final rectBeforeTap = tester.getRect (find.byWidget (headerRow! ));
486
+ final scrollableTop = tester.getRect (find.byType (Scrollable )).top;
487
+ // Check that the header is somewhere in the middle of the screen.
488
+ check (rectBeforeTap.top).isGreaterThan (scrollableTop);
489
+
490
+ await tapCollapseIcon (tester, 1 );
491
+
492
+ final headerRowAfterTap = findStreamHeaderRow (tester, 1 );
493
+ final rectAfterTap =
494
+ tester.getRect (find.byWidget (headerRowAfterTap! ));
495
+
496
+ // Check that the position of the header before and after
497
+ // collapsing is the same.
498
+ check (rectAfterTap).equals (rectBeforeTap);
499
+ });
500
+
388
501
// TODO check it remains collapsed even if you scroll far away and back
389
502
390
503
// TODO check that it's always uncollapsed when it appears after being
0 commit comments