11import 'dart:convert' ;
2+ import 'dart:io' ;
23
34import 'package:checks/checks.dart' ;
5+ import 'package:http/http.dart' as http;
46import 'package:test/scaffolding.dart' ;
57import 'package:zulip/api/model/events.dart' ;
68import 'package:zulip/api/model/model.dart' ;
79import 'package:zulip/api/model/submessage.dart' ;
10+ import 'package:zulip/api/route/messages.dart' ;
811import 'package:zulip/model/message_list.dart' ;
912import 'package:zulip/model/narrow.dart' ;
1013import 'package:zulip/model/store.dart' ;
@@ -13,6 +16,7 @@ import '../api/fake_api.dart';
1316import '../api/model/model_checks.dart' ;
1417import '../api/model/submessage_checks.dart' ;
1518import '../example_data.dart' as eg;
19+ import '../fake_async.dart' ;
1620import '../stdlib_checks.dart' ;
1721import 'message_list_test.dart' ;
1822import 'store_checks.dart' ;
@@ -123,6 +127,179 @@ void main() {
123127 });
124128 });
125129
130+ group ('edit-message methods' , () {
131+ late PerAccountStore store;
132+ late FakeApiConnection connection;
133+ late StreamMessage message;
134+
135+ Future <void > prepare () async {
136+ store = eg.store ();
137+ connection = store.connection as FakeApiConnection ;
138+ message = eg.streamMessage ();
139+ await store.addMessage (message);
140+ }
141+
142+ test ('smoke' , () => awaitFakeAsync ((async ) async {
143+ await prepare ();
144+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
145+
146+ connection.prepare (
147+ json: UpdateMessageResult ().toJson (), delay: Duration (seconds: 1 ));
148+ store.editMessage (messageId: message.id, content: 'new content' );
149+ check (connection.takeRequests ()).single.isA< http.Request > ()
150+ ..method.equals ('PATCH' )
151+ ..url.path.equals ('/api/v1/messages/${message .id }' )
152+ ..bodyFields.deepEquals ({
153+ 'content' : 'new content' ,
154+ });
155+
156+ async .elapse (Duration (milliseconds: 500 ));
157+ // Mid-request
158+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isFalse ();
159+
160+ async .elapse (Duration (milliseconds: 500 ));
161+ // Request has succeeded; event hasn't arrived
162+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isFalse ();
163+
164+ await store.handleEvent (eg.updateMessageEditEvent (message));
165+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
166+ }));
167+
168+ test ('request fails' , () => awaitFakeAsync ((async ) async {
169+ await prepare ();
170+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
171+
172+ connection.prepare (apiException: eg.apiBadRequest (), delay: Duration (seconds: 1 ));
173+ store.editMessage (messageId: message.id, content: 'new content' );
174+ async .elapse (Duration (seconds: 1 ));
175+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isTrue ();
176+ }));
177+
178+ test ('request fails; take failed edit' , () => awaitFakeAsync ((async ) async {
179+ await prepare ();
180+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
181+
182+ connection.prepare (apiException: eg.apiBadRequest (), delay: Duration (seconds: 1 ));
183+ store.editMessage (messageId: message.id, content: 'new content' );
184+ async .elapse (Duration (seconds: 1 ));
185+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isTrue ();
186+
187+ check (store.takeFailedMessageEdit (message.id)).equals ('new content' );
188+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
189+ }));
190+
191+ test ('takeFailedMessageEdit throws StateError when nothing to take' , () => awaitFakeAsync ((async ) async {
192+ await prepare ();
193+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
194+ check (() => store.takeFailedMessageEdit (message.id)).throws <StateError >();
195+ }));
196+
197+ test ('request failure after event arrival' , () => awaitFakeAsync ((async ) async {
198+ // This can happen with network issues.
199+
200+ await prepare ();
201+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
202+
203+ connection.prepare (
204+ httpException: const SocketException ('failed' ), delay: Duration (seconds: 1 ));
205+ store.editMessage (messageId: message.id, content: 'new content' );
206+
207+ async .elapse (Duration (milliseconds: 500 ));
208+ await store.handleEvent (eg.updateMessageEditEvent (message));
209+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
210+
211+ async .elapse (Duration (milliseconds: 500 ));
212+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
213+ }));
214+
215+ test ('request failure before event arrival' , () => awaitFakeAsync ((async ) async {
216+ // This can happen with network issues.
217+
218+ await prepare ();
219+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
220+
221+ connection.prepare (
222+ httpException: const SocketException ('failed' ), delay: Duration (seconds: 1 ));
223+ store.editMessage (messageId: message.id, content: 'new content' );
224+
225+ async .elapse (Duration (seconds: 1 ));
226+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isTrue ();
227+
228+ await store.handleEvent (eg.updateMessageEditEvent (message));
229+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
230+ }));
231+
232+ test ('request failure before event arrival; take failed edit in between' , () => awaitFakeAsync ((async ) async {
233+ // This can happen with network issues.
234+
235+ await prepare ();
236+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
237+
238+ connection.prepare (
239+ httpException: const SocketException ('failed' ), delay: Duration (seconds: 1 ));
240+ store.editMessage (messageId: message.id, content: 'new content' );
241+
242+ async .elapse (Duration (seconds: 1 ));
243+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isTrue ();
244+ check (store.takeFailedMessageEdit (message.id)).equals ('new content' );
245+
246+ await store.handleEvent (eg.updateMessageEditEvent (message)); // no error
247+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
248+ }));
249+
250+ test ('request fails, then message deleted' , () => awaitFakeAsync ((async ) async {
251+ await prepare ();
252+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
253+
254+ connection.prepare (apiException: eg.apiBadRequest (), delay: Duration (seconds: 1 ));
255+ store.editMessage (messageId: message.id, content: 'new content' );
256+ async .elapse (Duration (seconds: 1 ));
257+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isTrue ();
258+
259+ await store.handleEvent (eg.deleteMessageEvent ([message])); // no error
260+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
261+ }));
262+
263+ test ('message deleted while request in progress; we get failure response' , () => awaitFakeAsync ((async ) async {
264+ await prepare ();
265+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
266+
267+ connection.prepare (apiException: eg.apiBadRequest (), delay: Duration (seconds: 1 ));
268+ store.editMessage (messageId: message.id, content: 'new content' );
269+
270+ async .elapse (Duration (milliseconds: 500 ));
271+ // Mid-request
272+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isFalse ();
273+
274+ await store.handleEvent (eg.deleteMessageEvent ([message]));
275+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
276+
277+ async .elapse (Duration (milliseconds: 500 ));
278+ // Request failure, but status has already been cleared
279+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
280+ }));
281+
282+ test ('message deleted while request in progress but we get success response' , () => awaitFakeAsync ((async ) async {
283+ await prepare ();
284+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
285+
286+ connection.prepare (
287+ json: UpdateMessageResult ().toJson (), delay: Duration (seconds: 1 ));
288+ store.editMessage (messageId: message.id, content: 'new content' );
289+
290+ async .elapse (Duration (milliseconds: 500 ));
291+ // Mid-request
292+ check (store.getEditMessageErrorStatus (message.id)).isNotNull ().isFalse ();
293+
294+ await store.handleEvent (eg.deleteMessageEvent ([message]));
295+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
296+
297+ async .elapse (Duration (milliseconds: 500 ));
298+ // Request success
299+ check (store.getEditMessageErrorStatus (message.id)).isNull ();
300+ }));
301+ });
302+
126303 group ('handleMessageEvent' , () {
127304 test ('from empty' , () async {
128305 await prepare ();
0 commit comments