@@ -96,6 +96,23 @@ void main() {
9696 ..url.path.equals ('/api/v1/users/me/${narrow .streamId }/topics' );
9797 }
9898
99+ /// Set the content input's text to [content] , using [WidgetTester.enterText] .
100+ Future <void > enterContent (WidgetTester tester, {
101+ required ChannelNarrow narrow,
102+ required String content,
103+ }) async {
104+ final contentInputFinder = find.byWidgetPredicate (
105+ (widget) => widget is TextField && widget.controller is ComposeContentController );
106+
107+ await tester.enterText (contentInputFinder, content);
108+ }
109+
110+ Future <void > tapSendButton (WidgetTester tester) async {
111+ connection.prepare (json: SendMessageResult (id: 123 ).toJson ());
112+ await tester.tap (find.byIcon (ZulipIcons .send));
113+ await tester.pump (Duration .zero);
114+ }
115+
99116 group ('ComposeContentController' , () {
100117 group ('insertPadded' , () {
101118 // Like `parseMarkedText` in test/model/autocomplete_test.dart,
@@ -198,6 +215,95 @@ void main() {
198215 });
199216 });
200217
218+ group ('length validation' , () {
219+ final channel = eg.stream ();
220+
221+ /// String where the number of Unicode code points is [n]
222+ /// and the number of UTF-16 code units is higher.
223+ String makeStringWithCodePoints (int n) {
224+ assert (n >= 5 );
225+ const graphemeCluster = '👨👩👦' ;
226+ assert (graphemeCluster.runes.length == 5 );
227+ assert (graphemeCluster.length == 8 );
228+ assert (graphemeCluster.characters.length == 1 );
229+
230+ final result =
231+ graphemeCluster * (n ~ / 5 )
232+ + 'a' * (n % 5 );
233+ assert (result.runes.length == n);
234+
235+ return result;
236+ }
237+
238+ group ('content' , () {
239+ void doTest (String description, {required String content, required bool expectError}) {
240+ testWidgets (description, (tester) async {
241+ TypingNotifier .debugEnable = false ;
242+ addTearDown (TypingNotifier .debugReset);
243+
244+ final narrow = ChannelNarrow (channel.streamId);
245+ await prepareComposeBox (tester, narrow: narrow, streams: [channel]);
246+ await enterTopic (tester, narrow: narrow, topic: 'some topic' );
247+ await enterContent (tester, narrow: narrow, content: content);
248+
249+ await tapSendButton (tester);
250+ if (expectError) {
251+ await tester.tap (find.byWidget (checkErrorDialog (tester,
252+ expectedTitle: 'Message not sent' ,
253+ expectedMessage: 'Message length shouldn\' t be greater than 10000 characters.' )));
254+ } else {
255+ checkNoErrorDialog (tester);
256+ }
257+ });
258+ }
259+
260+ doTest ('too-long content is rejected' ,
261+ content: makeStringWithCodePoints (kMaxMessageLengthCodePoints + 1 ), expectError: true );
262+
263+ // TODO(#1238) unskip
264+ // doTest('max-length content not rejected',
265+ // content: makeStringWithCodePoints(kMaxMessageLengthCodePoints), expectError: false);
266+
267+ // TODO(#1238) replace with above commented-out test
268+ doTest ('some content not rejected' ,
269+ content: 'a' * kMaxMessageLengthCodePoints, expectError: false );
270+ });
271+
272+ group ('topic' , () {
273+ void doTest (String description, {required String topic, required bool expectError}) {
274+ testWidgets (description, (tester) async {
275+ TypingNotifier .debugEnable = false ;
276+ addTearDown (TypingNotifier .debugReset);
277+
278+ final narrow = ChannelNarrow (channel.streamId);
279+ await prepareComposeBox (tester, narrow: narrow, streams: [channel]);
280+ await enterTopic (tester, narrow: narrow, topic: topic);
281+ await enterContent (tester, narrow: narrow, content: 'some content' );
282+
283+ await tapSendButton (tester);
284+ if (expectError) {
285+ await tester.tap (find.byWidget (checkErrorDialog (tester,
286+ expectedTitle: 'Message not sent' ,
287+ expectedMessage: 'Topic length shouldn\' t be greater than 60 characters.' )));
288+ } else {
289+ checkNoErrorDialog (tester);
290+ }
291+ });
292+ }
293+
294+ doTest ('too-long topic is rejected' ,
295+ topic: makeStringWithCodePoints (kMaxTopicLengthCodePoints + 1 ), expectError: true );
296+
297+ // TODO(#1238) unskip
298+ // doTest('max-length topic not rejected',
299+ // topic: makeStringWithCodePoints(kMaxTopicLengthCodePoints), expectError: false);
300+
301+ // TODO(#1238) replace with above commented-out test
302+ doTest ('some topic not rejected' ,
303+ topic: 'a' * kMaxTopicLengthCodePoints, expectError: false );
304+ });
305+ });
306+
201307 group ('ComposeBox textCapitalization' , () {
202308 void checkComposeBoxTextFields (WidgetTester tester, {
203309 required bool expectTopicTextField,
0 commit comments