@@ -95,6 +95,23 @@ void main() {
95
95
..url.path.equals ('/api/v1/users/me/${narrow .streamId }/topics' );
96
96
}
97
97
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
+
98
115
group ('ComposeContentController' , () {
99
116
group ('insertPadded' , () {
100
117
// Like `parseMarkedText` in test/model/autocomplete_test.dart,
@@ -197,6 +214,95 @@ void main() {
197
214
});
198
215
});
199
216
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
+
200
306
group ('ComposeBox textCapitalization' , () {
201
307
void checkComposeBoxTextFields (WidgetTester tester, {
202
308
required bool expectTopicTextField,
0 commit comments