1
+ import 'dart:async' ;
1
2
import 'dart:convert' ;
2
3
3
4
import 'package:checks/checks.dart' ;
@@ -15,13 +16,16 @@ import 'package:zulip/model/localizations.dart';
15
16
import 'package:zulip/model/narrow.dart' ;
16
17
import 'package:zulip/model/store.dart' ;
17
18
import 'package:zulip/model/typing_status.dart' ;
19
+ import 'package:zulip/widgets/app.dart' ;
18
20
import 'package:zulip/widgets/compose_box.dart' ;
21
+ import 'package:zulip/widgets/page.dart' ;
19
22
20
23
import '../api/fake_api.dart' ;
21
24
import '../example_data.dart' as eg;
22
25
import '../flutter_checks.dart' ;
23
26
import '../model/binding.dart' ;
24
27
import '../model/test_store.dart' ;
28
+ import '../model/typing_status_test.dart' ;
25
29
import '../stdlib_checks.dart' ;
26
30
import 'dialog_checks.dart' ;
27
31
import 'test_app.dart' ;
@@ -32,6 +36,9 @@ void main() {
32
36
late PerAccountStore store;
33
37
late FakeApiConnection connection;
34
38
39
+ final contentInputFinder = find.byWidgetPredicate (
40
+ (widget) => widget is TextField && widget.controller is ComposeContentController );
41
+
35
42
Future <GlobalKey <ComposeBoxController >> prepareComposeBox (WidgetTester tester,
36
43
{required Narrow narrow, List <User > users = const []}) async {
37
44
addTearDown (testBinding.reset);
@@ -206,6 +213,139 @@ void main() {
206
213
});
207
214
});
208
215
216
+ group ('ComposeBox typing notices' , () {
217
+ const narrow = TopicNarrow (123 , 'some topic' );
218
+
219
+ void checkTypingRequest (TypingOp op, SendableNarrow narrow) =>
220
+ checkSetTypingStatusRequests (connection.takeRequests (), [(op, narrow)]);
221
+
222
+ Future <void > checkStartTyping (WidgetTester tester, SendableNarrow narrow) async {
223
+ connection.prepare (json: {});
224
+ await tester.enterText (contentInputFinder, 'hello world' );
225
+ checkTypingRequest (TypingOp .start, narrow);
226
+ }
227
+
228
+ testWidgets ('smoke TopicNarrow' , (tester) async {
229
+ await prepareComposeBox (tester, narrow: narrow);
230
+
231
+ await checkStartTyping (tester, narrow);
232
+
233
+ connection.prepare (json: {});
234
+ await tester.pump (store.typingNotifier.typingStoppedWaitPeriod);
235
+ checkTypingRequest (TypingOp .stop, narrow);
236
+ });
237
+
238
+ testWidgets ('smoke DmNarrow' , (tester) async {
239
+ final narrow = DmNarrow .withUsers (
240
+ [eg.otherUser.userId], selfUserId: eg.selfUser.userId);
241
+ await prepareComposeBox (tester, narrow: narrow);
242
+
243
+ await checkStartTyping (tester, narrow);
244
+
245
+ connection.prepare (json: {});
246
+ await tester.pump (store.typingNotifier.typingStoppedWaitPeriod);
247
+ checkTypingRequest (TypingOp .stop, narrow);
248
+ });
249
+
250
+ testWidgets ('smoke ChannelNarrow' , (tester) async {
251
+ const narrow = ChannelNarrow (123 );
252
+ final destinationNarrow = TopicNarrow (narrow.streamId, 'test topic' );
253
+ await prepareComposeBox (tester, narrow: narrow);
254
+ await enterTopic (tester, narrow: narrow, topic: destinationNarrow.topic);
255
+
256
+ await checkStartTyping (tester, destinationNarrow);
257
+
258
+ connection.prepare (json: {});
259
+ await tester.pump (store.typingNotifier.typingStoppedWaitPeriod);
260
+ checkTypingRequest (TypingOp .stop, destinationNarrow);
261
+ });
262
+
263
+ testWidgets ('clearing text sends a "typing stopped" notice' , (tester) async {
264
+ await prepareComposeBox (tester, narrow: narrow);
265
+
266
+ await checkStartTyping (tester, narrow);
267
+
268
+ connection.prepare (json: {});
269
+ await tester.enterText (contentInputFinder, '' );
270
+ checkTypingRequest (TypingOp .stop, narrow);
271
+ });
272
+
273
+ testWidgets ('hitting send button sends a "typing stopped" notice' , (tester) async {
274
+ await prepareComposeBox (tester, narrow: narrow);
275
+
276
+ await checkStartTyping (tester, narrow);
277
+
278
+ connection.prepare (json: {});
279
+ connection.prepare (json: SendMessageResult (id: 123 ).toJson ());
280
+ await tester.tap (find.byIcon (Icons .send));
281
+ await tester.pump (Duration .zero);
282
+ final requests = connection.takeRequests ();
283
+ checkSetTypingStatusRequests ([requests.first], [(TypingOp .stop, narrow)]);
284
+ check (requests).length.equals (2 );
285
+ });
286
+
287
+ Future <void > prepareComposeBoxWithNavigation (WidgetTester tester) async {
288
+ addTearDown (testBinding.reset);
289
+ await testBinding.globalStore.add (eg.selfAccount, eg.initialSnapshot ());
290
+
291
+ store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
292
+ connection = store.connection as FakeApiConnection ;
293
+
294
+ await tester.pumpWidget (const ZulipApp ());
295
+ await tester.pump ();
296
+ final navigator = await ZulipApp .navigator;
297
+ unawaited (navigator.push (MaterialAccountWidgetRoute (
298
+ accountId: eg.selfAccount.id, page: const ComposeBox (narrow: narrow))));
299
+ await tester.pumpAndSettle ();
300
+ }
301
+
302
+ testWidgets ('navigating away sends a "typing stopped" notice' , (tester) async {
303
+ await prepareComposeBoxWithNavigation (tester);
304
+
305
+ await checkStartTyping (tester, narrow);
306
+
307
+ connection.prepare (json: {});
308
+ (await ZulipApp .navigator).pop ();
309
+ await tester.pump (Duration .zero);
310
+ checkTypingRequest (TypingOp .stop, narrow);
311
+ });
312
+
313
+ testWidgets ('for content input, unfocusing sends a "typing stopped" notice' , (tester) async {
314
+ const narrow = ChannelNarrow (123 );
315
+ final destinationNarrow = TopicNarrow (narrow.streamId, 'test topic' );
316
+ await prepareComposeBox (tester, narrow: narrow);
317
+ await enterTopic (tester, narrow: narrow, topic: destinationNarrow.topic);
318
+
319
+ await checkStartTyping (tester, destinationNarrow);
320
+
321
+ connection.prepare (json: {});
322
+ FocusManager .instance.primaryFocus! .unfocus ();
323
+ await tester.pump (Duration .zero);
324
+ checkTypingRequest (TypingOp .stop, destinationNarrow);
325
+ });
326
+
327
+ testWidgets ('selection change sends a "typing started" notice' , (tester) async {
328
+ final controllerKey = await prepareComposeBox (tester, narrow: narrow);
329
+ final composeBoxController = controllerKey.currentState! ;
330
+
331
+ await checkStartTyping (tester, narrow);
332
+
333
+ connection.prepare (json: {});
334
+ await tester.pump (store.typingNotifier.typingStoppedWaitPeriod);
335
+ checkTypingRequest (TypingOp .stop, narrow);
336
+
337
+ connection.prepare (json: {});
338
+ composeBoxController.contentController.selection =
339
+ const TextSelection (baseOffset: 0 , extentOffset: 2 );
340
+ checkTypingRequest (TypingOp .start, narrow);
341
+
342
+ // Ensures that a "typing stopped" notice is sent when the test ends.
343
+ connection.prepare (json: {});
344
+ await tester.pump (store.typingNotifier.typingStoppedWaitPeriod);
345
+ checkTypingRequest (TypingOp .stop, narrow);
346
+ });
347
+ });
348
+
209
349
group ('message-send request response' , () {
210
350
Future <void > setupAndTapSend (WidgetTester tester, {
211
351
required void Function (int messageId) prepareResponse,
@@ -216,8 +356,6 @@ void main() {
216
356
final zulipLocalizations = GlobalLocalizations .zulipLocalizations;
217
357
await prepareComposeBox (tester, narrow: const TopicNarrow (123 , 'some topic' ));
218
358
219
- final contentInputFinder = find.byWidgetPredicate (
220
- (widget) => widget is TextField && widget.controller is ComposeContentController );
221
359
await tester.enterText (contentInputFinder, 'hello world' );
222
360
223
361
prepareResponse (456 );
0 commit comments