Skip to content

Commit cdb2dd2

Browse files
committed
compose: Fix error on quote-and-replying message from unknown sender
Related: #716
1 parent 46dbe1e commit cdb2dd2

File tree

2 files changed

+62
-14
lines changed

2 files changed

+62
-14
lines changed

lib/model/compose.dart

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,33 @@ String wrapWithBacktickFence({required String content, String? infoString}) {
130130
/// To omit the user ID part ("|13313") whenever the name part is unambiguous,
131131
/// pass the full UserStore. This means accepting a linear scan
132132
/// through all users; avoid it in performance-sensitive codepaths.
133+
///
134+
/// See also [userMentionFromMessage].
133135
String userMention(User user, {bool silent = false, UserStore? users}) {
134136
bool includeUserId = users == null
135137
|| users.allUsers.where((u) => u.fullName == user.fullName)
136138
.take(2).length == 2;
137-
138-
return '@${silent ? '_' : ''}**${user.fullName}${includeUserId ? '|${user.userId}' : ''}**';
139+
return _userMentionImpl(
140+
silent: silent,
141+
fullName: user.fullName,
142+
userId: includeUserId ? user.userId : null);
139143
}
140144

145+
/// An @-mention of an individual user, like @**Chris Bobbe|13313**,
146+
/// from sender data in a [Message].
147+
///
148+
/// The user ID part ("|13313") is always included.
149+
///
150+
/// See also [userMention].
151+
String userMentionFromMessage(Message message, {bool silent = false, required UserStore users}) =>
152+
_userMentionImpl(
153+
silent: silent,
154+
fullName: users.senderDisplayName(message),
155+
userId: message.senderId);
156+
157+
String _userMentionImpl({required bool silent, required String fullName, int? userId}) =>
158+
'@${silent ? '_' : ''}**$fullName${userId != null ? '|$userId' : ''}**';
159+
141160
/// An @-mention of all the users in a conversation, like @**channel**.
142161
String wildcardMention(WildcardMentionOption wildcardOption, {
143162
required PerAccountStore store,
@@ -190,13 +209,11 @@ String quoteAndReplyPlaceholder(
190209
PerAccountStore store, {
191210
required Message message,
192211
}) {
193-
final sender = store.getUser(message.senderId);
194-
assert(sender != null); // TODO(#716): should use `store.senderDisplayName`
195212
final url = narrowLink(store,
196213
SendableNarrow.ofMessage(message, selfUserId: store.selfUserId),
197214
nearMessageId: message.id);
198-
// See note in [quoteAndReply] about asking `mention` to omit the |<id> part.
199-
return '${userMention(sender!, silent: true)} ${inlineLink('said', url)}: ' // TODO(#1285)
215+
return '${userMentionFromMessage(message, silent: true, users: store)} '
216+
'${inlineLink('said', url)}: ' // TODO(#1285)
200217
'*${zulipLocalizations.composeBoxLoadingMessage(message.id)}*\n';
201218
}
202219

@@ -212,14 +229,10 @@ String quoteAndReply(PerAccountStore store, {
212229
required Message message,
213230
required String rawContent,
214231
}) {
215-
final sender = store.getUser(message.senderId);
216-
assert(sender != null); // TODO(#716): should use `store.senderDisplayName`
217232
final url = narrowLink(store,
218233
SendableNarrow.ofMessage(message, selfUserId: store.selfUserId),
219234
nearMessageId: message.id);
220-
// Could ask `mention` to omit the |<id> part unless the mention is ambiguous…
221-
// but that would mean a linear scan through all users, and the extra noise
222-
// won't much matter with the already probably-long message link in there too.
223-
return '${userMention(sender!, silent: true)} ${inlineLink('said', url)}:\n' // TODO(#1285)
235+
return '${userMentionFromMessage(message, silent: true, users: store)} '
236+
'${inlineLink('said', url)}:\n' // TODO(#1285)
224237
'${wrapWithBacktickFence(content: rawContent, infoString: 'quote')}';
225238
}

test/model/compose_test.dart

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:checks/checks.dart';
22
import 'package:test/scaffolding.dart';
3+
import 'package:zulip/api/model/events.dart';
34
import 'package:zulip/model/compose.dart';
45
import 'package:zulip/model/localizations.dart';
56
import 'package:zulip/model/store.dart';
@@ -225,26 +226,60 @@ hello
225226
group('mention', () {
226227
group('user', () {
227228
final user = eg.user(userId: 123, fullName: 'Full Name');
228-
test('not silent', () {
229+
final message = eg.streamMessage(sender: user);
230+
test('not silent', () async {
231+
final store = eg.store();
232+
await store.addUser(user);
229233
check(userMention(user, silent: false)).equals('@**Full Name|123**');
234+
check(userMentionFromMessage(message, silent: false, users: store))
235+
.equals('@**Full Name|123**');
230236
});
231-
test('silent', () {
237+
test('silent', () async {
238+
final store = eg.store();
239+
await store.addUser(user);
232240
check(userMention(user, silent: true)).equals('@_**Full Name|123**');
241+
check(userMentionFromMessage(message, silent: true, users: store))
242+
.equals('@_**Full Name|123**');
233243
});
234244
test('`users` passed; has two users with same fullName', () async {
235245
final store = eg.store();
236246
await store.addUsers([user, eg.user(userId: 5), eg.user(userId: 234, fullName: user.fullName)]);
237247
check(userMention(user, silent: true, users: store)).equals('@_**Full Name|123**');
248+
check(userMentionFromMessage(message, silent: true, users: store))
249+
.equals('@_**Full Name|123**');
238250
});
239251
test('`users` passed; has two same-name users but one of them is deactivated', () async {
240252
final store = eg.store();
241253
await store.addUsers([user, eg.user(userId: 5), eg.user(userId: 234, fullName: user.fullName, isActive: false)]);
242254
check(userMention(user, silent: true, users: store)).equals('@_**Full Name|123**');
255+
check(userMentionFromMessage(message, silent: true, users: store))
256+
.equals('@_**Full Name|123**');
243257
});
244258
test('`users` passed; user has unique fullName', () async {
245259
final store = eg.store();
246260
await store.addUsers([user, eg.user(userId: 234, fullName: 'Another Name')]);
247261
check(userMention(user, silent: true, users: store)).equals('@_**Full Name**');
262+
check(userMentionFromMessage(message, silent: true, users: store))
263+
.equals('@_**Full Name|123**');
264+
});
265+
266+
test('userMentionFromMessage, known user', () async {
267+
final user = eg.user(userId: 123, fullName: 'Full Name');
268+
final store = eg.store();
269+
await store.addUser(user);
270+
check(userMentionFromMessage(message, silent: false, users: store))
271+
.equals('@**Full Name|123**');
272+
await store.handleEvent(RealmUserUpdateEvent(id: 1,
273+
userId: user.userId, fullName: 'New Name'));
274+
check(userMentionFromMessage(message, silent: false, users: store))
275+
.equals('@**New Name|123**');
276+
});
277+
278+
test('userMentionFromMessage, unknown user', () async {
279+
final store = eg.store();
280+
check(store.getUser(user.userId)).isNull();
281+
check(userMentionFromMessage(message, silent: false, users: store))
282+
.equals('@**Full Name|123**');
248283
});
249284
});
250285

0 commit comments

Comments
 (0)