@@ -104,6 +104,12 @@ void main() {
104104 await tester.enterText (contentInputFinder, content);
105105 }
106106
107+ Future <void > tapSendButton (WidgetTester tester) async {
108+ connection.prepare (json: SendMessageResult (id: 123 ).toJson ());
109+ await tester.tap (find.byIcon (ZulipIcons .send));
110+ await tester.pump (Duration .zero);
111+ }
112+
107113 group ('ComposeContentController' , () {
108114 group ('insertPadded' , () {
109115 // Like `parseMarkedText` in test/model/autocomplete_test.dart,
@@ -206,6 +212,107 @@ void main() {
206212 });
207213 });
208214
215+ group ('length validation' , () {
216+ final channel = eg.stream ();
217+
218+ /// String where there are [n] Unicode code points,
219+ /// >[n] UTF-16 code units, and <[n] "characters" a.k.a. grapheme clusters.
220+ String makeStringWithCodePoints (int n) {
221+ assert (n >= 5 );
222+ const graphemeCluster = '👨👩👦' ;
223+ assert (graphemeCluster.runes.length == 5 );
224+ assert (graphemeCluster.length == 8 );
225+ assert (graphemeCluster.characters.length == 1 );
226+
227+ final result =
228+ graphemeCluster * (n ~ / 5 )
229+ + 'a' * (n % 5 );
230+ assert (result.runes.length == n);
231+
232+ return result;
233+ }
234+
235+ group ('content' , () {
236+ Future <void > prepareWithContent (WidgetTester tester, String content) async {
237+ TypingNotifier .debugEnable = false ;
238+ addTearDown (TypingNotifier .debugReset);
239+
240+ final narrow = ChannelNarrow (channel.streamId);
241+ await prepareComposeBox (tester, narrow: narrow, streams: [channel]);
242+ await enterTopic (tester, narrow: narrow, topic: 'some topic' );
243+ await enterContent (tester, content);
244+ }
245+
246+ Future <void > checkErrorResponse (WidgetTester tester) async {
247+ await tester.tap (find.byWidget (checkErrorDialog (tester,
248+ expectedTitle: 'Message not sent' ,
249+ expectedMessage: 'Message length shouldn\' t be greater than 10000 characters.' )));
250+ }
251+
252+ testWidgets ('too-long content is rejected' , (tester) async {
253+ await prepareWithContent (tester,
254+ makeStringWithCodePoints (kMaxMessageLengthCodePoints + 1 ));
255+ await tapSendButton (tester);
256+ await checkErrorResponse (tester);
257+ });
258+
259+ // TODO(#1238) unskip
260+ // testWidgets('max-length content not rejected', (tester) async {
261+ // await prepareWithContent(tester,
262+ // makeStringWithCodePoints(kMaxMessageLengthCodePoints));
263+ // await tapSendButton(tester);
264+ // checkNoErrorDialog(tester);
265+ // });
266+
267+ // TODO(#1238) replace with above commented-out test
268+ testWidgets ('some content not rejected' , (tester) async {
269+ await prepareWithContent (tester, 'a' * kMaxMessageLengthCodePoints);
270+ await tapSendButton (tester);
271+ checkNoErrorDialog (tester);
272+ });
273+ });
274+
275+ group ('topic' , () {
276+ Future <void > prepareWithTopic (WidgetTester tester, String topic) async {
277+ TypingNotifier .debugEnable = false ;
278+ addTearDown (TypingNotifier .debugReset);
279+
280+ final narrow = ChannelNarrow (channel.streamId);
281+ await prepareComposeBox (tester, narrow: narrow, streams: [channel]);
282+ await enterTopic (tester, narrow: narrow, topic: topic);
283+ await enterContent (tester, 'some content' );
284+ }
285+
286+ Future <void > checkErrorResponse (WidgetTester tester) async {
287+ await tester.tap (find.byWidget (checkErrorDialog (tester,
288+ expectedTitle: 'Message not sent' ,
289+ expectedMessage: 'Topic length shouldn\' t be greater than 60 characters.' )));
290+ }
291+
292+ testWidgets ('too-long topic is rejected' , (tester) async {
293+ await prepareWithTopic (tester,
294+ makeStringWithCodePoints (kMaxTopicLengthCodePoints + 1 ));
295+ await tapSendButton (tester);
296+ await checkErrorResponse (tester);
297+ });
298+
299+ // TODO(#1238) unskip
300+ // testWidgets('max-length topic not rejected', (tester) async {
301+ // await prepareWithTopic(tester,
302+ // makeStringWithCodePoints(kMaxTopicLengthCodePoints));
303+ // await tapSendButton(tester);
304+ // checkNoErrorDialog(tester);
305+ // });
306+
307+ // TODO(#1238) replace with above commented-out test
308+ testWidgets ('some topic not rejected' , (tester) async {
309+ await prepareWithTopic (tester, 'a' * kMaxTopicLengthCodePoints);
310+ await tapSendButton (tester);
311+ checkNoErrorDialog (tester);
312+ });
313+ });
314+ });
315+
209316 group ('ComposeBox textCapitalization' , () {
210317 void checkComposeBoxTextFields (WidgetTester tester, {
211318 required bool expectTopicTextField,
0 commit comments