@@ -77,12 +77,15 @@ void main() {
7777 ///
7878 /// For example, "~@chris^" means the text is "@chris", the selection is
7979 /// collapsed at index 6, and we expect the syntax to start at index 0.
80- void doTest (String markedText, ComposeAutocompleteQuery ? expectedQuery) {
80+ void doTest (String markedText, ComposeAutocompleteQuery ? expectedQuery, {
81+ int ? maxChannelName,
82+ }) {
8183 final description = expectedQuery != null
8284 ? 'in ${jsonEncode (markedText )}, query ${jsonEncode (expectedQuery .raw )}'
8385 : 'no query in ${jsonEncode (markedText )}' ;
8486 test (description, () {
85- store = eg.store ();
87+ store = eg.store (initialSnapshot:
88+ eg.initialSnapshot (maxChannelNameLength: maxChannelName));
8689 final controller = ComposeContentController (store: store);
8790 final parsed = parseMarkedText (markedText);
8891 assert ((expectedQuery == null ) == (parsed.expectedSyntaxStart == null ));
@@ -99,6 +102,7 @@ void main() {
99102
100103 MentionAutocompleteQuery mention (String raw) => MentionAutocompleteQuery (raw, silent: false );
101104 MentionAutocompleteQuery silentMention (String raw) => MentionAutocompleteQuery (raw, silent: true );
105+ ChannelLinkAutocompleteQuery channelLink (String raw) => ChannelLinkAutocompleteQuery (raw);
102106 EmojiAutocompleteQuery emoji (String raw) => EmojiAutocompleteQuery (raw);
103107
104108 doTest ('' , null );
@@ -180,8 +184,101 @@ void main() {
180184 doTest ('~@_Rodion Romanovich Raskolniko^' , silentMention ('Rodion Romanovich Raskolniko' ));
181185 doTest ('~@Родион Романович Раскольников^' , mention ('Родион Романович Раскольников' ));
182186 doTest ('~@_Родион Романович Раскольнико^' , silentMention ('Родион Романович Раскольнико' ));
183- doTest ('If @chris is around, please ask him.^' , null ); // @ sign is too far away from cursor
184- doTest ('If @_chris is around, please ask him.^' , null ); // @ sign is too far away from cursor
187+
188+ // @ sign can be (3 + maxChannelName) characters away to the left of cursor.
189+ doTest ('If ~@chris^ is around, please ask him.' , mention ('chris' ), maxChannelName: 20 );
190+ doTest ('If ~@_chris is^ around, please ask him.' , silentMention ('chris is' ), maxChannelName: 20 );
191+ doTest ('If @chris is around, please ask him.^' , null , maxChannelName: 20 );
192+ doTest ('If @_chris is around, please ask him.^' , null , maxChannelName: 20 );
193+
194+ // #channel link.
195+
196+ doTest ('^#' , null );
197+ doTest ('^#abc' , null );
198+ doTest ('#abc' , null ); // (no cursor)
199+
200+ doTest ('~#^' , channelLink ('' ));
201+ doTest ('~##^' , channelLink ('#' ));
202+ doTest ('~#abc^' , channelLink ('abc' ));
203+ doTest ('~#abc ^' , channelLink ('abc ' ));
204+ doTest ('~#abc def^' , channelLink ('abc def' ));
205+
206+ // Accept space before channel link syntax.
207+ doTest (' ~#abc^' , channelLink ('abc' ));
208+ doTest ('xyz ~#abc^' , channelLink ('abc' ));
209+
210+ // Accept punctuations before channel link syntax.
211+ doTest (':~#abc^' , channelLink ('abc' ));
212+ doTest ('!~#abc^' , channelLink ('abc' ));
213+ doTest (',~#abc^' , channelLink ('abc' ));
214+ doTest ('.~#abc^' , channelLink ('abc' ));
215+ doTest ('(~#abc^' , channelLink ('abc' )); doTest (')~#abc^' , channelLink ('abc' ));
216+ doTest ('{~#abc^' , channelLink ('abc' )); doTest ('}~#abc^' , channelLink ('abc' ));
217+ doTest ('[~#abc^' , channelLink ('abc' )); doTest (']~#abc^' , channelLink ('abc' ));
218+ doTest ('“~#abc^' , channelLink ('abc' )); doTest ('”~#abc^' , channelLink ('abc' ));
219+ doTest ('«~#abc^' , channelLink ('abc' )); doTest ('»~#abc^' , channelLink ('abc' ));
220+ // … and other punctuations except '#' and '@':
221+ doTest ('~##abc^' , channelLink ('#abc' ));
222+ doTest ('~@#abc^' , mention ('#abc' ));
223+
224+ // Avoid other characters before channel link syntax.
225+ doTest ('+#abc^' , null );
226+ doTest ('=#abc^' , null );
227+ doTest ('\$ #abc^' , null );
228+ doTest ('zulip/zulip-flutter#124^' , null );
229+ doTest ('XYZ#abc^' , null );
230+ doTest ('xyz#abc^' , null );
231+ // … but
232+ doTest ('~#xyz#abc^' , channelLink ('xyz#abc' ));
233+
234+ // Avoid leading space character in query.
235+ doTest ('# ^' , null );
236+ doTest ('# abc^' , null );
237+
238+ // Avoid line-break characters in query.
239+ doTest ('#\n ^' , null ); doTest ('#a\n ^' , null ); doTest ('#\n a^' , null ); doTest ('#a\n b^' , null );
240+ doTest ('#\r ^' , null ); doTest ('#a\r ^' , null ); doTest ('#\r a^' , null ); doTest ('#a\r b^' , null );
241+ doTest ('#\r\n ^' , null ); doTest ('#a\r\n ^' , null ); doTest ('#\r\n a^' , null ); doTest ('#a\r\n b^' , null );
242+
243+ // Allow all other sorts of characters in query.
244+ doTest ('~#\u 0000^' , channelLink ('\u 0000' )); // control
245+ doTest ('~#\u 061C^' , channelLink ('\u 061C' )); // format character
246+ doTest ('~#\u 0600^' , channelLink ('\u 0600' )); // format
247+ doTest ('~#\u D834^' , channelLink ('\u D834' )); // leading surrogate
248+ doTest ('~#`^' , channelLink ('`' )); doTest ('~#a`b^' , channelLink ('a`b' ));
249+ doTest ('~#\\ ^' , channelLink ('\\ ' )); doTest ('~#a\\ b^' , channelLink ('a\\ b' ));
250+ doTest ('~#"^' , channelLink ('"' )); doTest ('~#a"b^' , channelLink ('a"b' ));
251+ doTest ('~#>^' , channelLink ('>' )); doTest ('~#a>b^' , channelLink ('a>b' ));
252+ doTest ('~#&^' , channelLink ('&' )); doTest ('~#a&b^' , channelLink ('a&b' ));
253+ doTest ('~#_^' , channelLink ('_' )); doTest ('~#a_b^' , channelLink ('a_b' ));
254+ doTest ('~#*^' , channelLink ('*' )); doTest ('~#a*b^' , channelLink ('a*b' ));
255+
256+ // Two leading stars ('**') in the query are omitted.
257+ doTest ('~#**^' , channelLink ('' ));
258+ doTest ('~#**abc^' , channelLink ('abc' ));
259+ doTest ('~#**abc ^' , channelLink ('abc ' ));
260+ doTest ('~#**abc def^' , channelLink ('abc def' ));
261+ doTest ('#** ^' , null );
262+ doTest ('#** abc^' , null );
263+
264+ doTest ('~#**abc*^' , channelLink ('abc*' ));
265+
266+ // Query with leading '**' should not contain other '**'.
267+ doTest ('#**abc**^' , null );
268+ doTest ('#**abc** ^' , null );
269+ doTest ('#**abc** def^' , null );
270+
271+ // Query without leading '**' can contain other '**'.
272+ doTest ('~#abc**^' , channelLink ('abc**' ));
273+ doTest ('~#abc** ^' , channelLink ('abc** ' ));
274+ doTest ('~#abc** def^' , channelLink ('abc** def' ));
275+ doTest ('~#*abc**^' , channelLink ('*abc**' ));
276+
277+ // "#" sign can be (3 + maxChannelName) characters away to the left of cursor.
278+ doTest ('check ~#**mobile dev^ team' , channelLink ('mobile dev' ), maxChannelName: 10 );
279+ doTest ('check ~#mobile dev t^eam' , channelLink ('mobile dev t' ), maxChannelName: 10 );
280+ doTest ('check #mobile dev te^am' , null , maxChannelName: 10 );
281+ doTest ('check #mobile dev team for more info^' , null , maxChannelName: 10 );
185282
186283 // Emoji (":smile:").
187284
0 commit comments