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