@@ -14,8 +14,10 @@ import 'package:zulip/widgets/message_list.dart';
1414import 'package:zulip/widgets/topic_list.dart' ;
1515
1616import '../api/fake_api.dart' ;
17+ import '../api/route/route_checks.dart' ;
1718import '../example_data.dart' as eg;
1819import '../model/binding.dart' ;
20+ import '../model/store_checks.dart' ;
1921import '../model/test_store.dart' ;
2022import '../stdlib_checks.dart' ;
2123import 'test_app.dart' ;
@@ -124,7 +126,7 @@ void main() {
124126 check (find.byType (CircularProgressIndicator )).findsNothing ();
125127 });
126128
127- testWidgets (' fetch again when navigating away and back' , (tester) async {
129+ testWidgets ("dont't fetch again when navigating away and back" , (tester) async {
128130 addTearDown (testBinding.reset);
129131 await testBinding.globalStore.add (eg.selfAccount, eg.initialSnapshot ());
130132 final store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
@@ -145,20 +147,28 @@ void main() {
145147 await tester.tap (find.byIcon (ZulipIcons .topics));
146148 await tester.pump ();
147149 await tester.pump (Duration .zero);
150+ check (connection.takeRequests ())
151+ ..length.equals (2 ) // one for the messages request, another for the topics
152+ ..last.which ((last) => last
153+ .isA< http.Request > ()
154+ ..method.equals ('GET' )
155+ ..url.path.equals ('/api/v1/users/me/${channel .streamId }/topics' ));
148156 check (find.text ('topic A' )).findsOne ();
149157
150158 // … go back to the message list page…
151159 await tester.pageBack ();
152160 await tester.pump ();
153161
154- // … then back to the topic-list page, expecting to fetch again.
162+ // … then back to the topic-list page, expecting not to fetch again but
163+ // use existing data which is kept up-to-date anyways.
155164 connection.prepare (json: GetStreamTopicsResult (
156165 topics: [eg.getStreamTopicsEntry (name: 'topic B' )]).toJson ());
157166 await tester.tap (find.byIcon (ZulipIcons .topics));
158167 await tester.pump ();
159168 await tester.pump (Duration .zero);
160- check (find.text ('topic A' )).findsNothing ();
161- check (find.text ('topic B' )).findsOne ();
169+ check (connection.takeRequests ()).isEmpty ();
170+ check (find.text ('topic B' )).findsNothing ();
171+ check (find.text ('topic A' )).findsOne ();
162172 });
163173
164174 Finder topicItemFinder = find.descendant (
@@ -169,6 +179,18 @@ void main() {
169179 of: topicItemFinder.at (index),
170180 matching: finder);
171181
182+ testWidgets ('sort topics by maxId' , (tester) async {
183+ await prepare (tester, topics: [
184+ eg.getStreamTopicsEntry (name: 'A' , maxId: 3 ),
185+ eg.getStreamTopicsEntry (name: 'B' , maxId: 2 ),
186+ eg.getStreamTopicsEntry (name: 'C' , maxId: 4 ),
187+ ]);
188+
189+ check (findInTopicItemAt (0 , find.text ('C' ))).findsOne ();
190+ check (findInTopicItemAt (1 , find.text ('A' ))).findsOne ();
191+ check (findInTopicItemAt (2 , find.text ('B' ))).findsOne ();
192+ });
193+
172194 testWidgets ('show topic action sheet' , (tester) async {
173195 final channel = eg.stream ();
174196 await prepare (tester, channel: channel,
@@ -190,16 +212,72 @@ void main() {
190212 });
191213 });
192214
193- testWidgets ('sort topics by maxId' , (tester) async {
194- await prepare (tester, topics: [
195- eg.getStreamTopicsEntry (name: 'A' , maxId: 3 ),
196- eg.getStreamTopicsEntry (name: 'B' , maxId: 2 ),
197- eg.getStreamTopicsEntry (name: 'C' , maxId: 4 ),
198- ]);
215+ testWidgets ('show topic action sheet before and after moves' , (tester) async {
216+ final channel = eg.stream ();
217+ final message = eg.streamMessage (id: 123 , stream: channel, topic: 'foo' );
218+ await prepare (tester, channel: channel,
219+ topics: [eg.getStreamTopicsEntry (name: 'foo' , maxId: 123 )],
220+ messages: [message]);
221+ check (store).getStreamTopics (channel.streamId).isNotNull ().single
222+ .maxId.equals (123 );
223+ check (topicItemFinder).findsOne ();
224+
225+ // Before the move, "foo"'s maxId is known to be accurate. This makes
226+ // topic actions that require `someMessageIdInTopic` available.
227+ await tester.longPress (find.text ('foo' ));
228+ await tester.pump (Duration (milliseconds: 150 )); // bottom-sheet animation
229+ check (find.text ('Mark as resolved' )).findsOne ();
230+ await tester.tap (find.text ('Cancel' ));
199231
200- check (findInTopicItemAt (0 , find.text ('C' ))).findsOne ();
201- check (findInTopicItemAt (1 , find.text ('A' ))).findsOne ();
202- check (findInTopicItemAt (2 , find.text ('B' ))).findsOne ();
232+ await store.handleEvent (eg.updateMessageEventMoveFrom (
233+ origMessages: [message],
234+ newTopicStr: 'bar' ));
235+ await tester.pump ();
236+ check (topicItemFinder).findsExactly (2 );
237+
238+ // After the move, the message with maxId moved away from "foo". The topic
239+ // actions that require `someMessageIdInTopic` is no longer available.
240+ await tester.longPress (find.text ('foo' ));
241+ await tester.pump (Duration (milliseconds: 150 )); // bottom-sheet animation
242+ check (find.text ('Mark as resolved' )).findsNothing ();
243+ await tester.tap (find.text ('Cancel' ));
244+ await tester.pump ();
245+
246+ // Topic actions that require `someMessageIdInTopic` is available
247+ // for "bar", the new topic that the message moved to.
248+ await tester.longPress (find.text ('bar' ));
249+ await tester.pump (Duration (milliseconds: 150 )); // bottom-sheet animation
250+ check (find.text ('Mark as resolved' )).findsOne ();
251+ });
252+
253+ // event handling is more thoroughly tested in test/model/channel_test.dart
254+ testWidgets ('smoke resolve topic from topic action sheet' , (tester) async {
255+ final channel = eg.stream ();
256+ final messages = List .generate (10 , (i) =>
257+ eg.streamMessage (id: 100 + i, stream: channel, topic: 'foo' ));
258+
259+ await prepare (tester, channel: channel,
260+ topics: [eg.getStreamTopicsEntry (maxId: messages.last.id, name: 'foo' )],
261+ messages: messages);
262+ await tester.longPress (topicItemFinder);
263+ await tester.pump (Duration (milliseconds: 150 )); // bottom-sheet animation
264+ check (findInTopicItemAt (0 , find.byIcon (ZulipIcons .check))).findsNothing ();
265+ check (find.descendant (of: topicItemFinder,
266+ matching: find.text ('foo' ))).findsOne ();
267+
268+ connection.prepare (json: {});
269+ await tester.tap (find.text ('Mark as resolved' ));
270+ await tester.pump ();
271+ await tester.pump (Duration .zero);
272+
273+ await store.handleEvent (eg.updateMessageEventMoveFrom (
274+ origMessages: messages,
275+ newTopic: eg.t ('foo' ).resolve (),
276+ propagateMode: PropagateMode .changeAll));
277+ await tester.pump ();
278+ check (findInTopicItemAt (0 , find.byIcon (ZulipIcons .check))).findsOne ();
279+ check (find.descendant (of: topicItemFinder,
280+ matching: find.text ('foo' ))).findsOne ();
203281 });
204282
205283 testWidgets ('resolved and unresolved topics' , (tester) async {
0 commit comments