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