typeahead: Nest User inside UserPillData, UserOrMention.

Commit 33484e7ac3 (#29200) added a cache
for remove_diacritics, but this caching was rendered ineffective by
commit 45e9c046d8 (#29650) because it
relied on mutating a direct reference to the User object.  Fix the
cache by rearranging the types to preserve that direct reference.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2024-06-06 13:55:50 -07:00 committed by Tim Abbott
parent 2340855851
commit aaf90a1da0
11 changed files with 379 additions and 344 deletions

View File

@ -206,10 +206,10 @@ export function warn_if_mentioning_unsubscribed_user(
return; return;
} }
if (mentioned.is_broadcast) { if (mentioned.type === "broadcast") {
return; // don't check if @all/@everyone/@stream return; // don't check if @all/@everyone/@stream
} }
const user_id = mentioned.user_id; const user_id = mentioned.user.user_id;
const stream_id = get_stream_id_for_textarea($textarea); const stream_id = get_stream_id_for_textarea($textarea);
if (!stream_id) { if (!stream_id) {
@ -237,7 +237,7 @@ export function warn_if_mentioning_unsubscribed_user(
? $t({defaultMessage: "Subscribe them"}) ? $t({defaultMessage: "Subscribe them"})
: null, : null,
can_subscribe_other_users, can_subscribe_other_users,
name: mentioned.full_name, name: mentioned.user.full_name,
classname: compose_banner.CLASSNAMES.recipient_not_subscribed, classname: compose_banner.CLASSNAMES.recipient_not_subscribed,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(user_id), should_add_guest_user_indicator: people.should_add_guest_user_indicator(user_id),
}; };

View File

@ -33,7 +33,7 @@ import * as stream_topic_history from "./stream_topic_history";
import * as stream_topic_history_util from "./stream_topic_history_util"; import * as stream_topic_history_util from "./stream_topic_history_util";
import * as timerender from "./timerender"; import * as timerender from "./timerender";
import * as typeahead_helper from "./typeahead_helper"; import * as typeahead_helper from "./typeahead_helper";
import type {UserOrMention, UserOrMentionPillData} from "./typeahead_helper"; import type {UserOrMentionPillData} from "./typeahead_helper";
import type {UserGroupPillData} from "./user_group_pill"; import type {UserGroupPillData} from "./user_group_pill";
import * as user_groups from "./user_groups"; import * as user_groups from "./user_groups";
import type {UserGroup} from "./user_groups"; import type {UserGroup} from "./user_groups";
@ -493,7 +493,6 @@ export function broadcast_mentions(): PseudoMentionUser[] {
pm_recipient_count: Number.POSITIVE_INFINITY, pm_recipient_count: Number.POSITIVE_INFINITY,
full_name: mention, full_name: mention,
is_broadcast: true,
// used for sorting // used for sorting
idx, idx,
@ -596,15 +595,8 @@ export function get_pm_people(query: string): (UserGroupPillData | UserPillData)
// to do this. // to do this.
const user_suggestions: (UserGroupPillData | UserPillData)[] = []; const user_suggestions: (UserGroupPillData | UserPillData)[] = [];
for (const suggestion of suggestions) { for (const suggestion of suggestions) {
if (suggestion.type === "user_or_mention") { assert(suggestion.type !== "broadcast");
assert(suggestion.is_broadcast === undefined); user_suggestions.push(suggestion);
user_suggestions.push({
...suggestion,
type: "user",
});
} else {
user_suggestions.push(suggestion);
}
} }
return user_suggestions; return user_suggestions;
} }
@ -634,18 +626,20 @@ export function get_person_suggestions(
} }
// Exclude muted users from typeaheads. // Exclude muted users from typeaheads.
persons = muted_users.filter_muted_users(persons); persons = muted_users.filter_muted_users(persons);
let user_or_mention_list: UserOrMention[] = persons.map((person) => ({ let person_items: UserOrMentionPillData[] = persons.map((person) => ({
...person, type: "user",
is_broadcast: undefined, user: person,
})); }));
if (opts.want_broadcast) { if (opts.want_broadcast) {
user_or_mention_list = [...user_or_mention_list, ...broadcast_mentions()]; person_items = [
...person_items,
...broadcast_mentions().map((mention) => ({
type: "broadcast" as const,
user: mention,
})),
];
} }
const person_items: UserOrMentionPillData[] = user_or_mention_list.map((person) => ({
...person,
type: "user_or_mention",
}));
return person_items.filter((item) => typeahead_helper.query_matches_person(query, item)); return person_items.filter((item) => typeahead_helper.query_matches_person(query, item));
} }
@ -975,7 +969,8 @@ export function content_highlighter_html(item: TypeaheadSuggestion): string | un
case "emoji": case "emoji":
return typeahead_helper.render_emoji(item); return typeahead_helper.render_emoji(item);
case "user_group": case "user_group":
case "user_or_mention": case "user":
case "broadcast":
return typeahead_helper.render_person_or_user_group(item); return typeahead_helper.render_person_or_user_group(item);
case "slash": case "slash":
return typeahead_helper.render_typeahead_item({ return typeahead_helper.render_typeahead_item({
@ -1041,7 +1036,8 @@ export function content_typeahead_selected(
} }
break; break;
case "user_group": case "user_group":
case "user_or_mention": { case "user":
case "broadcast": {
const is_silent = item.is_silent; const is_silent = item.is_silent;
beginning = beginning.slice(0, -token.length - 1); beginning = beginning.slice(0, -token.length - 1);
if (beginning.endsWith("@_*")) { if (beginning.endsWith("@_*")) {
@ -1061,14 +1057,18 @@ export function content_typeahead_selected(
// that functionality yet, and we haven't gotten much // that functionality yet, and we haven't gotten much
// feedback on this being an actual pitfall. // feedback on this being an actual pitfall.
} else { } else {
const user_id = item.is_broadcast ? undefined : item.user_id; const user_id = item.type === "broadcast" ? undefined : item.user.user_id;
let mention_text = people.get_mention_syntax(item.full_name, user_id, is_silent); let mention_text = people.get_mention_syntax(
if (!is_silent && !item.is_broadcast) { item.user.full_name,
user_id,
is_silent,
);
if (!is_silent && item.type !== "broadcast") {
compose_validate.warn_if_mentioning_unsubscribed_user(item, $textbox); compose_validate.warn_if_mentioning_unsubscribed_user(item, $textbox);
mention_text = compose_validate.convert_mentions_to_silent_in_direct_messages( mention_text = compose_validate.convert_mentions_to_silent_in_direct_messages(
mention_text, mention_text,
item.full_name, item.user.full_name,
item.user_id, item.user.user_id,
); );
} }
beginning += mention_text + " "; beginning += mention_text + " ";
@ -1358,7 +1358,7 @@ export function initialize({
pill_widget.clear_text(); pill_widget.clear_text();
} }
} else { } else {
compose_pm_pill.set_from_typeahead(item); compose_pm_pill.set_from_typeahead(item.user);
} }
}, },
stopAdvance: true, // Do not advance to the next field on a Tab or Enter stopAdvance: true, // Do not advance to the next field on a Tab or Enter

View File

@ -69,7 +69,6 @@ export type PseudoMentionUser = {
email: string; email: string;
pm_recipient_count: number; pm_recipient_count: number;
full_name: string; full_name: string;
is_broadcast: true;
idx: number; idx: number;
}; };

View File

@ -16,7 +16,8 @@ import type {UserPillData} from "./user_pill";
function person_matcher(query: string, item: UserPillData): boolean { function person_matcher(query: string, item: UserPillData): boolean {
return ( return (
people.is_known_user_id(item.user_id) && typeahead_helper.query_matches_person(query, item) people.is_known_user_id(item.user.user_id) &&
typeahead_helper.query_matches_person(query, item)
); );
} }
@ -73,10 +74,9 @@ export function set_up(
// If user_source is specified in opts, it // If user_source is specified in opts, it
// is given priority. Otherwise we use // is given priority. Otherwise we use
// default user_pill.typeahead_source. // default user_pill.typeahead_source.
const users: UserPillData[] = opts.user_source().map((user) => ({ const users: UserPillData[] = opts
...user, .user_source()
type: "user", .map((user) => ({type: "user", user}));
}));
source = [...source, ...users]; source = [...source, ...users];
} else { } else {
source = [...source, ...user_pill.typeahead_source(pills, exclude_bots)]; source = [...source, ...user_pill.typeahead_source(pills, exclude_bots)];
@ -132,7 +132,7 @@ export function set_up(
const users: UserPillData[] = []; const users: UserPillData[] = [];
if (include_users) { if (include_users) {
for (const match of matches) { for (const match of matches) {
if (match.type === "user" && people.is_known_user_id(match.user_id)) { if (match.type === "user" && people.is_known_user_id(match.user.user_id)) {
users.push(match); users.push(match);
} }
} }
@ -164,9 +164,9 @@ export function set_up(
} else if ( } else if (
include_users && include_users &&
item.type === "user" && item.type === "user" &&
people.is_known_user_id(item.user_id) people.is_known_user_id(item.user.user_id)
) { ) {
user_pill.append_user(item, pills); user_pill.append_user(item.user, pills);
} }
$input.trigger("focus"); $input.trigger("focus");

View File

@ -26,9 +26,10 @@ import * as user_status from "./user_status";
import type {UserStatusEmojiInfo} from "./user_status"; import type {UserStatusEmojiInfo} from "./user_status";
import * as util from "./util"; import * as util from "./util";
export type UserOrMention = PseudoMentionUser | (User & {is_broadcast: undefined}); export type UserOrMention =
| {type: "broadcast"; user: PseudoMentionUser}
| {type: "user"; user: User};
export type UserOrMentionPillData = UserOrMention & { export type UserOrMentionPillData = UserOrMention & {
type: "user_or_mention";
is_silent?: boolean; is_silent?: boolean;
}; };
@ -116,32 +117,34 @@ export function render_typeahead_item(args: {
} }
export function render_person(person: UserPillData | UserOrMentionPillData): string { export function render_person(person: UserPillData | UserOrMentionPillData): string {
if (person.type === "user_or_mention" && person.is_broadcast) { if (person.type === "broadcast") {
return render_typeahead_item({ return render_typeahead_item({
primary: person.special_item_text, primary: person.user.special_item_text,
is_person: true, is_person: true,
}); });
} }
const user_circle_class = buddy_data.get_user_circle_class(person.user_id); const user_circle_class = buddy_data.get_user_circle_class(person.user.user_id);
const avatar_url = people.small_avatar_url_for_person(person); const avatar_url = people.small_avatar_url_for_person(person.user);
const status_emoji_info = user_status.get_status_emoji(person.user_id); const status_emoji_info = user_status.get_status_emoji(person.user.user_id);
const PRONOUNS_ID = realm.custom_profile_field_types.PRONOUNS.id; const PRONOUNS_ID = realm.custom_profile_field_types.PRONOUNS.id;
const pronouns_list = people.get_custom_fields_by_type(person.user_id, PRONOUNS_ID); const pronouns_list = people.get_custom_fields_by_type(person.user.user_id, PRONOUNS_ID);
const pronouns = pronouns_list?.[0]?.value; const pronouns = pronouns_list?.[0]?.value;
const typeahead_arguments = { const typeahead_arguments = {
primary: person.full_name, primary: person.user.full_name,
img_src: avatar_url, img_src: avatar_url,
user_circle_class, user_circle_class,
is_person: true, is_person: true,
status_emoji_info, status_emoji_info,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(person.user_id), should_add_guest_user_indicator: people.should_add_guest_user_indicator(
person.user.user_id,
),
pronouns, pronouns,
secondary: person.delivery_email, secondary: person.user.delivery_email,
}; };
return render_typeahead_item(typeahead_arguments); return render_typeahead_item(typeahead_arguments);
@ -249,43 +252,31 @@ export function compare_people_for_relevance(
current_stream_id?: number, current_stream_id?: number,
): number { ): number {
// give preference to "all", "everyone" or "stream" // give preference to "all", "everyone" or "stream"
// We use is_broadcast for a quick check. It will
// true for all/everyone/stream and undefined (falsy)
// for actual people.
const person_a_is_broadcast = person_a.type === "user_or_mention" && person_a.is_broadcast;
const person_b_is_broadcast = person_b.type === "user_or_mention" && person_b.is_broadcast;
if (compose_state.get_message_type() !== "private") { if (compose_state.get_message_type() !== "private") {
if (person_a_is_broadcast) { if (person_a.type === "broadcast") {
if (person_b_is_broadcast) { if (person_b.type === "broadcast") {
return person_a.idx - person_b.idx; return person_a.user.idx - person_b.user.idx;
} }
return -1; return -1;
} else if (person_b_is_broadcast) { } else if (person_b.type === "broadcast") {
return 1; return 1;
} }
} else { } else {
if (person_a_is_broadcast) { if (person_a.type === "broadcast") {
if (person_b_is_broadcast) { if (person_b.type === "broadcast") {
return person_a.idx - person_b.idx; return person_a.user.idx - person_b.user.idx;
} }
return 1; return 1;
} else if (person_b_is_broadcast) { } else if (person_b.type === "broadcast") {
return -1; return -1;
} }
} }
// Now handle actual people users. // Now handle actual people users.
assert(
(person_a.type === "user_or_mention" && !person_a.is_broadcast) || person_a.type === "user",
);
assert(
(person_b.type === "user_or_mention" && !person_b.is_broadcast) || person_b.type === "user",
);
// give preference to subscribed users first // give preference to subscribed users first
if (current_stream_id !== undefined) { if (current_stream_id !== undefined) {
const a_is_sub = stream_data.is_user_subscribed(current_stream_id, person_a.user_id); const a_is_sub = stream_data.is_user_subscribed(current_stream_id, person_a.user.user_id);
const b_is_sub = stream_data.is_user_subscribed(current_stream_id, person_b.user_id); const b_is_sub = stream_data.is_user_subscribed(current_stream_id, person_b.user.user_id);
if (a_is_sub && !b_is_sub) { if (a_is_sub && !b_is_sub) {
return -1; return -1;
@ -295,13 +286,13 @@ export function compare_people_for_relevance(
} }
if (compare_by_current_conversation !== undefined) { if (compare_by_current_conversation !== undefined) {
const preference = compare_by_current_conversation(person_a, person_b); const preference = compare_by_current_conversation(person_a.user, person_b.user);
if (preference !== 0) { if (preference !== 0) {
return preference; return preference;
} }
} }
return compare_by_pms(person_a, person_b); return compare_by_pms(person_a.user, person_b.user);
} }
export function sort_people_for_relevance<UserType extends UserOrMentionPillData | UserPillData>( export function sort_people_for_relevance<UserType extends UserOrMentionPillData | UserPillData>(
@ -444,7 +435,7 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
return sort_people_for_relevance(items, current_stream_id, current_topic); return sort_people_for_relevance(items, current_stream_id, current_topic);
} }
const users_name_results = typeahead.triage_raw(query, users, (p) => p.full_name); const users_name_results = typeahead.triage_raw(query, users, (p) => p.user.full_name);
const users_name_good_matches = [ const users_name_good_matches = [
...users_name_results.exact_matches, ...users_name_results.exact_matches,
...users_name_results.begins_with_case_sensitive_matches, ...users_name_results.begins_with_case_sensitive_matches,
@ -455,7 +446,7 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
const email_results = typeahead.triage_raw( const email_results = typeahead.triage_raw(
query, query,
users_name_results.no_matches, users_name_results.no_matches,
(p) => p.email, (p) => p.user.email,
); );
const email_good_matches = [ const email_good_matches = [
...email_results.exact_matches, ...email_results.exact_matches,
@ -523,11 +514,13 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
function add_user_recipients(items: UserType[]): void { function add_user_recipients(items: UserType[]): void {
for (const item of items) { for (const item of items) {
const item_is_broadcast = item.type === "user_or_mention" && item.is_broadcast; if (
const topic_wildcard_mention = item.email === "topic"; item.type !== "broadcast" ||
if (!item_is_broadcast || topic_wildcard_mention || !stream_wildcard_mention_included) { item.user.email === "topic" ||
!stream_wildcard_mention_included
) {
recipients.push(item); recipients.push(item);
if (item_is_broadcast && !topic_wildcard_mention) { if (item.type === "broadcast" && item.user.email !== "topic") {
stream_wildcard_mention_included = true; stream_wildcard_mention_included = true;
} }
} }
@ -654,16 +647,13 @@ export function query_matches_person(
query: string, query: string,
person: UserPillData | UserOrMentionPillData, person: UserPillData | UserOrMentionPillData,
): boolean { ): boolean {
if (typeahead.query_matches_string_in_order(query, person.full_name, " ")) { if (typeahead.query_matches_string_in_order(query, person.user.full_name, " ")) {
return true; return true;
} }
if ( if (person.type === "user" && Boolean(person.user.delivery_email)) {
(person.type === "user" || person.is_broadcast === undefined) &&
Boolean(person.delivery_email)
) {
return typeahead.query_matches_string_in_order( return typeahead.query_matches_string_in_order(
query, query,
people.get_visible_email(person), people.get_visible_email(person.user),
" ", " ",
); );
} }

View File

@ -18,7 +18,7 @@ export type UserPill = {
export type UserPillWidget = InputPillContainer<UserPill>; export type UserPillWidget = InputPillContainer<UserPill>;
export type UserPillData = User & {type: "user"}; export type UserPillData = {type: "user"; user: User};
export function create_item_from_email( export function create_item_from_email(
email: string, email: string,
@ -136,10 +136,7 @@ export function typeahead_source(
exclude_bots?: boolean, exclude_bots?: boolean,
): UserPillData[] { ): UserPillData[] {
const users = exclude_bots ? people.get_realm_active_human_users() : people.get_realm_users(); const users = exclude_bots ? people.get_realm_active_human_users() : people.get_realm_users();
return filter_taken_users(users, pill_widget).map((user) => ({ return filter_taken_users(users, pill_widget).map((user) => ({type: "user", user}));
...user,
type: "user",
}));
} }
export function filter_taken_users( export function filter_taken_users(

View File

@ -666,7 +666,9 @@ test_ui("warn_if_mentioning_unsubscribed_user", ({override, mock_template}) => {
override(settings_data, "user_can_subscribe_other_users", () => true); override(settings_data, "user_can_subscribe_other_users", () => true);
let mentioned_details = { let mentioned_details = {
email: "foo@bar.com", user: {
email: "foo@bar.com",
},
}; };
let new_banner_rendered = false; let new_banner_rendered = false;
@ -679,19 +681,19 @@ test_ui("warn_if_mentioning_unsubscribed_user", ({override, mock_template}) => {
return "<banner-stub>"; return "<banner-stub>";
}); });
function test_noop_case(is_private, is_zephyr_mirror, is_broadcast) { function test_noop_case(is_private, is_zephyr_mirror, type) {
new_banner_rendered = false; new_banner_rendered = false;
const msg_type = is_private ? "private" : "stream"; const msg_type = is_private ? "private" : "stream";
compose_state.set_message_type(msg_type); compose_state.set_message_type(msg_type);
realm.realm_is_zephyr_mirror_realm = is_zephyr_mirror; realm.realm_is_zephyr_mirror_realm = is_zephyr_mirror;
mentioned_details.is_broadcast = is_broadcast; mentioned_details.type = type;
compose_validate.warn_if_mentioning_unsubscribed_user(mentioned_details, $textarea); compose_validate.warn_if_mentioning_unsubscribed_user(mentioned_details, $textarea);
assert.ok(!new_banner_rendered); assert.ok(!new_banner_rendered);
} }
test_noop_case(true, false, false); test_noop_case(true, false, "user");
test_noop_case(false, true, false); test_noop_case(false, true, "user");
test_noop_case(false, false, true); test_noop_case(false, false, "broadcast");
$("#compose_invite_users").hide(); $("#compose_invite_users").hide();
compose_state.set_message_type("stream"); compose_state.set_message_type("stream");
@ -717,11 +719,14 @@ test_ui("warn_if_mentioning_unsubscribed_user", ({override, mock_template}) => {
// Test mentioning a user that should gets a warning. // Test mentioning a user that should gets a warning.
mentioned_details = { mentioned_details = {
email: "foo@bar.com", type: "user",
user_id: 34, user: {
full_name: "Foo Barson", email: "foo@bar.com",
user_id: 34,
full_name: "Foo Barson",
},
}; };
people.add_active_user(mentioned_details); people.add_active_user(mentioned_details.user);
new_banner_rendered = false; new_banner_rendered = false;
const $banner_container = $("#compose_banners"); const $banner_container = $("#compose_banners");

View File

@ -61,20 +61,12 @@ const ct = composebox_typeahead;
// broadcast-mentions/persons/groups. // broadcast-mentions/persons/groups.
ct.__Rewire__("max_num_items", 15); ct.__Rewire__("max_num_items", 15);
function user_or_mention_item(item) { function user_item(user) {
return { return {type: "user", user};
is_broadcast: undefined, // default, overridden by `item`
...item,
type: "user_or_mention",
};
} }
function user_item(item) { function broadcast_item(user) {
return { return {type: "broadcast", user};
...item,
is_broadcast: undefined,
type: "user",
};
} }
function slash_item(slash) { function slash_item(slash) {
@ -333,90 +325,102 @@ function emoji_objects(emoji_names) {
return emoji_names.map((emoji_name) => emoji_list_by_name.get(emoji_name)); return emoji_names.map((emoji_name) => emoji_list_by_name.get(emoji_name));
} }
const ali = user_or_mention_item({ const ali = {
email: "ali@zulip.com", email: "ali@zulip.com",
user_id: 98, user_id: 98,
full_name: "Ali", full_name: "Ali",
is_moderator: false, is_moderator: false,
}); };
const ali_item = user_item(ali);
const alice = user_or_mention_item({ const alice = {
email: "alice@zulip.com", email: "alice@zulip.com",
user_id: 99, user_id: 99,
full_name: "Alice", full_name: "Alice",
is_moderator: false, is_moderator: false,
}); };
const alice_item = user_item(alice);
const hamlet = user_or_mention_item({ const hamlet = {
email: "hamlet@zulip.com", email: "hamlet@zulip.com",
user_id: 100, user_id: 100,
full_name: "King Hamlet", full_name: "King Hamlet",
is_moderator: false, is_moderator: false,
}); };
const hamlet_item = user_item(hamlet);
const othello = user_or_mention_item({ const othello = {
email: "othello@zulip.com", email: "othello@zulip.com",
user_id: 101, user_id: 101,
full_name: "Othello, the Moor of Venice", full_name: "Othello, the Moor of Venice",
is_moderator: false, is_moderator: false,
delivery_email: null, delivery_email: null,
}); };
const othello_item = user_item(othello);
const cordelia = user_or_mention_item({ const cordelia = {
email: "cordelia@zulip.com", email: "cordelia@zulip.com",
user_id: 102, user_id: 102,
full_name: "Cordelia, Lear's daughter", full_name: "Cordelia, Lear's daughter",
is_moderator: false, is_moderator: false,
}); };
const cordelia_item = user_item(cordelia);
const deactivated_user = user_or_mention_item({ const deactivated_user = {
email: "other@zulip.com", email: "other@zulip.com",
user_id: 103, user_id: 103,
full_name: "Deactivated User", full_name: "Deactivated User",
is_moderator: false, is_moderator: false,
}); };
const deactivated_user_item = user_item(deactivated_user);
const lear = user_or_mention_item({ const lear = {
email: "lear@zulip.com", email: "lear@zulip.com",
user_id: 104, user_id: 104,
full_name: "King Lear", full_name: "King Lear",
is_moderator: false, is_moderator: false,
}); };
const lear_item = user_item(lear);
const twin1 = user_or_mention_item({ const twin1 = {
full_name: "Mark Twin", full_name: "Mark Twin",
is_moderator: false, is_moderator: false,
user_id: 105, user_id: 105,
email: "twin1@zulip.com", email: "twin1@zulip.com",
}); };
const twin1_item = user_item(twin1);
const twin2 = user_or_mention_item({ const twin2 = {
full_name: "Mark Twin", full_name: "Mark Twin",
is_moderator: false, is_moderator: false,
user_id: 106, user_id: 106,
email: "twin2@zulip.com", email: "twin2@zulip.com",
}); };
const twin2_item = user_item(twin2);
const gael = user_or_mention_item({ const gael = {
full_name: "Gaël Twin", full_name: "Gaël Twin",
is_moderator: false, is_moderator: false,
user_id: 107, user_id: 107,
email: "twin3@zulip.com", email: "twin3@zulip.com",
}); };
const gael_item = user_item(gael);
const hal = user_or_mention_item({ const hal = {
full_name: "Earl Hal", full_name: "Earl Hal",
is_moderator: false, is_moderator: false,
user_id: 108, user_id: 108,
email: "hal@zulip.com", email: "hal@zulip.com",
}); };
const hal_item = user_item(hal);
const harry = user_or_mention_item({ const harry = {
full_name: "Harry", full_name: "Harry",
is_moderator: false, is_moderator: false,
user_id: 109, user_id: 109,
email: "harry@zulip.com", email: "harry@zulip.com",
}); };
const harry_item = user_item(harry);
const hamletcharacters = user_group_item({ const hamletcharacters = user_group_item({
name: "hamletcharacters", name: "hamletcharacters",
@ -457,17 +461,17 @@ const make_emoji = (emoji_dict) => ({
// Sorted by name // Sorted by name
const sorted_user_list = [ const sorted_user_list = [
ali, ali_item,
alice, alice_item,
cordelia, cordelia_item,
hal, // Early Hal hal_item, // Early Hal
gael, gael_item,
harry, harry_item,
hamlet, // King Hamlet hamlet_item, // King Hamlet
lear, lear_item,
twin1, // Mark Twin twin1_item, // Mark Twin
twin2, twin2_item,
othello, othello_item,
]; ];
function test(label, f) { function test(label, f) {
@ -585,38 +589,38 @@ test("content_typeahead_selected", ({override}) => {
query = "@**Mark Tw"; query = "@**Mark Tw";
ct.get_or_set_token_for_testing("Mark Tw"); ct.get_or_set_token_for_testing("Mark Tw");
actual_value = ct.content_typeahead_selected(twin1, query, input_element); actual_value = ct.content_typeahead_selected(twin1_item, query, input_element);
expected_value = "@**Mark Twin|105** "; expected_value = "@**Mark Twin|105** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
let warned_for_mention = false; let warned_for_mention = false;
override(compose_validate, "warn_if_mentioning_unsubscribed_user", (mentioned) => { override(compose_validate, "warn_if_mentioning_unsubscribed_user", (mentioned) => {
assert.equal(mentioned, othello); assert.equal(mentioned, othello_item);
warned_for_mention = true; warned_for_mention = true;
}); });
query = "@oth"; query = "@oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected(othello, query, input_element); actual_value = ct.content_typeahead_selected(othello_item, query, input_element);
expected_value = "@**Othello, the Moor of Venice** "; expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
assert.ok(warned_for_mention); assert.ok(warned_for_mention);
query = "Hello @oth"; query = "Hello @oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected(othello, query, input_element); actual_value = ct.content_typeahead_selected(othello_item, query, input_element);
expected_value = "Hello @**Othello, the Moor of Venice** "; expected_value = "Hello @**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
query = "@**oth"; query = "@**oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected(othello, query, input_element); actual_value = ct.content_typeahead_selected(othello_item, query, input_element);
expected_value = "@**Othello, the Moor of Venice** "; expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
query = "@*oth"; query = "@*oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected(othello, query, input_element); actual_value = ct.content_typeahead_selected(othello_item, query, input_element);
expected_value = "@**Othello, the Moor of Venice** "; expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -638,7 +642,7 @@ test("content_typeahead_selected", ({override}) => {
// silent mention // silent mention
ct.get_or_set_completing_for_tests("silent_mention"); ct.get_or_set_completing_for_tests("silent_mention");
const silent_hamlet = { const silent_hamlet = {
...hamlet, ...hamlet_item,
is_silent: true, is_silent: true,
}; };
query = "@_kin"; query = "@_kin";
@ -944,23 +948,21 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// This should match the users added at the beginning of this test file. // This should match the users added at the beginning of this test file.
let actual_value = options.source(""); let actual_value = options.source("");
let expected_value = [ let expected_value = [
user_item(ali), ali_item,
user_item(alice), alice_item,
user_item(cordelia), cordelia_item,
user_item(hal), hal_item,
user_item(gael), gael_item,
user_item(harry), harry_item,
user_item(hamlet), hamlet_item,
user_item(lear), lear_item,
user_item(twin1), twin1_item,
user_item(twin2), twin2_item,
user_item(othello), othello_item,
hamletcharacters, hamletcharacters,
backend, backend,
call_center, call_center,
]; ];
actual_value.sort((a, b) => a.user_id - b.user_id);
expected_value.sort((a, b) => a.user_id - b.user_id);
assert.deepEqual(actual_value, expected_value); assert.deepEqual(actual_value, expected_value);
function matcher(query, person) { function matcher(query, person) {
@ -970,46 +972,46 @@ test("initialize", ({override, override_rewire, mock_template}) => {
let query; let query;
query = "el"; // Matches both "othELlo" and "cordELia" query = "el"; // Matches both "othELlo" and "cordELia"
assert.equal(matcher(query, othello), true); assert.equal(matcher(query, othello_item), true);
assert.equal(matcher(query, cordelia), true); assert.equal(matcher(query, cordelia_item), true);
query = "bender"; // Doesn't exist query = "bender"; // Doesn't exist
assert.equal(matcher(query, othello), false); assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia), false); assert.equal(matcher(query, cordelia_item), false);
query = "gael"; query = "gael";
assert.equal(matcher(query, gael), true); assert.equal(matcher(query, gael_item), true);
query = "Gaël"; query = "Gaël";
assert.equal(matcher(query, gael), true); assert.equal(matcher(query, gael_item), true);
query = "gaël"; query = "gaël";
assert.equal(matcher(query, gael), true); assert.equal(matcher(query, gael_item), true);
// Don't make suggestions if the last name only has whitespaces // Don't make suggestions if the last name only has whitespaces
// (we're between typing names). // (we're between typing names).
query = "othello@zulip.com, "; query = "othello@zulip.com, ";
assert.equal(matcher(query, othello), false); assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia), false); assert.equal(matcher(query, cordelia_item), false);
// query = 'othello@zulip.com,, , cord'; // query = 'othello@zulip.com,, , cord';
query = "cord"; query = "cord";
assert.equal(matcher(query, othello), false); assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia), true); assert.equal(matcher(query, cordelia_item), true);
// If the user is already in the list, typeahead doesn't include it // If the user is already in the list, typeahead doesn't include it
// again. // again.
query = "cordelia@zulip.com, cord"; query = "cordelia@zulip.com, cord";
assert.equal(matcher(query, othello), false); assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia), false); assert.equal(matcher(query, cordelia_item), false);
// Matching by email // Matching by email
query = "oth"; query = "oth";
deactivated_user.delivery_email = null; deactivated_user.delivery_email = null;
assert.equal(matcher(query, deactivated_user), false); assert.equal(matcher(query, deactivated_user_item), false);
deactivated_user.delivery_email = "other@zulip.com"; deactivated_user.delivery_email = "other@zulip.com";
assert.equal(matcher(query, deactivated_user), true); assert.equal(matcher(query, deactivated_user_item), true);
function sorter(query, people) { function sorter(query, people) {
return typeahead_helper.sort_recipients({ return typeahead_helper.sort_recipients({
@ -1024,21 +1026,21 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// beginning first, and then the rest of them in REVERSE order of // beginning first, and then the rest of them in REVERSE order of
// the input. // the input.
query = "othello"; query = "othello";
actual_value = sorter(query, [othello]); actual_value = sorter(query, [othello_item]);
expected_value = [othello]; expected_value = [othello_item];
assert.deepEqual(actual_value, expected_value); assert.deepEqual(actual_value, expected_value);
query = "Ali"; query = "Ali";
actual_value = sorter(query, [alice, ali]); actual_value = sorter(query, [alice_item, ali_item]);
expected_value = [ali, alice]; expected_value = [ali_item, alice_item];
assert.deepEqual(actual_value, expected_value); assert.deepEqual(actual_value, expected_value);
// A literal match at the beginning of an element puts it at the top. // A literal match at the beginning of an element puts it at the top.
query = "co"; // Matches everything ("x@zulip.COm") query = "co"; // Matches everything ("x@zulip.COm")
actual_value = sorter(query, [othello, deactivated_user, cordelia]); actual_value = sorter(query, [othello_item, deactivated_user_item, cordelia_item]);
expected_value = [cordelia, deactivated_user, othello]; expected_value = [cordelia_item, deactivated_user_item, othello_item];
actual_value.sort((a, b) => a.user_id - b.user_id); actual_value.sort((a, b) => a.user.user_id - b.user.user_id);
expected_value.sort((a, b) => a.user_id - b.user_id); expected_value.sort((a, b) => a.user.user_id - b.user.user_id);
assert.deepEqual(actual_value, expected_value); assert.deepEqual(actual_value, expected_value);
query = "non-existing-user"; query = "non-existing-user";
@ -1050,8 +1052,8 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// if there wasn't any logic replacing `no break-space` // if there wasn't any logic replacing `no break-space`
// with normal space. // with normal space.
query = "cordelia, lear's\u00A0"; query = "cordelia, lear's\u00A0";
assert.equal(matcher(query, cordelia), true); assert.equal(matcher(query, cordelia_item), true);
assert.equal(matcher(query, othello), false); assert.equal(matcher(query, othello_item), false);
const event = { const event = {
target: "#doesnotmatter", target: "#doesnotmatter",
@ -1060,12 +1062,12 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// options.updater() // options.updater()
options.query = "othello"; options.query = "othello";
appended_names = []; appended_names = [];
options.updater(othello, event); options.updater(othello_item, event);
assert.deepEqual(appended_names, ["Othello, the Moor of Venice"]); assert.deepEqual(appended_names, ["Othello, the Moor of Venice"]);
options.query = "othello@zulip.com, cor"; options.query = "othello@zulip.com, cor";
appended_names = []; appended_names = [];
actual_value = options.updater(cordelia, event); actual_value = options.updater(cordelia_item, event);
assert.deepEqual(appended_names, ["Cordelia, Lear's daughter"]); assert.deepEqual(appended_names, ["Cordelia, Lear's daughter"]);
const click_event = {type: "click", target: "#doesnotmatter"}; const click_event = {type: "click", target: "#doesnotmatter"};
@ -1073,7 +1075,7 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// Focus lost (caused by the click event in the typeahead list) // Focus lost (caused by the click event in the typeahead list)
$("#private_message_recipient").trigger("blur"); $("#private_message_recipient").trigger("blur");
appended_names = []; appended_names = [];
actual_value = options.updater(othello, click_event); actual_value = options.updater(othello_item, click_event);
assert.deepEqual(appended_names, ["Othello, the Moor of Venice"]); assert.deepEqual(appended_names, ["Othello, the Moor of Venice"]);
cleared = false; cleared = false;
@ -1120,7 +1122,7 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// content_highlighter_html. // content_highlighter_html.
ct.get_or_set_completing_for_tests("mention"); ct.get_or_set_completing_for_tests("mention");
ct.get_or_set_token_for_testing("othello"); ct.get_or_set_token_for_testing("othello");
actual_value = options.highlighter_html(othello); actual_value = options.highlighter_html(othello_item);
expected_value = expected_value =
` <span class="user_circle_empty user_circle"></span>\n` + ` <span class="user_circle_empty user_circle"></span>\n` +
` <img class="typeahead-image" src="http://zulip.zulipdev.com/avatar/${othello.user_id}?s&#x3D;50" />\n` + ` <img class="typeahead-image" src="http://zulip.zulipdev.com/avatar/${othello.user_id}?s&#x3D;50" />\n` +
@ -1486,7 +1488,7 @@ test("begins_typeahead", ({override, override_rewire}) => {
}, },
]); ]);
const mention_all = user_or_mention_item(ct.broadcast_mentions()[0]); const mention_all = broadcast_item(ct.broadcast_mentions()[0]);
const users_and_all_mention = [...sorted_user_list, mention_all]; const users_and_all_mention = [...sorted_user_list, mention_all];
const users_and_user_groups = [ const users_and_user_groups = [
...sorted_user_list, ...sorted_user_list,
@ -1495,7 +1497,7 @@ test("begins_typeahead", ({override, override_rewire}) => {
backend, backend,
call_center, // "folks working in support" call_center, // "folks working in support"
]; ];
const mention_everyone = user_or_mention_item(ct.broadcast_mentions()[1]); const mention_everyone = broadcast_item(ct.broadcast_mentions()[1]);
function mentions_with_silent_marker(mentions, is_silent) { function mentions_with_silent_marker(mentions, is_silent) {
return mentions.map((item) => ({ return mentions.map((item) => ({
...item, ...item,
@ -1515,25 +1517,34 @@ test("begins_typeahead", ({override, override_rewire}) => {
assert_typeahead_equals("@_**", mentions_with_silent_marker(users_and_user_groups, true)); assert_typeahead_equals("@_**", mentions_with_silent_marker(users_and_user_groups, true));
assert_typeahead_equals( assert_typeahead_equals(
"test @**o", "test @**o",
mentions_with_silent_marker([othello, cordelia, mention_everyone], false), mentions_with_silent_marker([othello_item, cordelia_item, mention_everyone], false),
);
assert_typeahead_equals(
"test @_**o",
mentions_with_silent_marker([othello_item, cordelia_item], true),
); );
assert_typeahead_equals("test @_**o", mentions_with_silent_marker([othello, cordelia], true));
assert_typeahead_equals( assert_typeahead_equals(
"test @*o", "test @*o",
mentions_with_silent_marker([othello, cordelia, mention_everyone], false), mentions_with_silent_marker([othello_item, cordelia_item, mention_everyone], false),
); );
assert_typeahead_equals( assert_typeahead_equals(
"test @_*k", "test @_*k",
mentions_with_silent_marker([hamlet, lear, twin1, twin2, backend], true), mentions_with_silent_marker(
[hamlet_item, lear_item, twin1_item, twin2_item, backend],
true,
),
); );
assert_typeahead_equals( assert_typeahead_equals(
"test @*h", "test @*h",
mentions_with_silent_marker([harry, hal, hamlet, cordelia, othello], false), mentions_with_silent_marker(
[harry_item, hal_item, hamlet_item, cordelia_item, othello_item],
false,
),
); );
assert_typeahead_equals( assert_typeahead_equals(
"test @_*h", "test @_*h",
mentions_with_silent_marker( mentions_with_silent_marker(
[harry, hal, hamlet, hamletcharacters, cordelia, othello], [harry_item, hal_item, hamlet_item, hamletcharacters, cordelia_item, othello_item],
true, true,
), ),
); );
@ -1550,28 +1561,69 @@ test("begins_typeahead", ({override, override_rewire}) => {
assert_typeahead_equals( assert_typeahead_equals(
"test\n@i", "test\n@i",
mentions_with_silent_marker( mentions_with_silent_marker(
[ali, alice, cordelia, gael, hamlet, lear, twin1, twin2, othello], [
ali_item,
alice_item,
cordelia_item,
gael_item,
hamlet_item,
lear_item,
twin1_item,
twin2_item,
othello_item,
],
false, false,
), ),
); );
assert_typeahead_equals( assert_typeahead_equals(
"test\n@_i", "test\n@_i",
mentions_with_silent_marker( mentions_with_silent_marker(
[ali, alice, cordelia, gael, hamlet, lear, twin1, twin2, othello], [
ali_item,
alice_item,
cordelia_item,
gael_item,
hamlet_item,
lear_item,
twin1_item,
twin2_item,
othello_item,
],
true, true,
), ),
); );
assert_typeahead_equals( assert_typeahead_equals(
"test\n @l", "test\n @l",
mentions_with_silent_marker( mentions_with_silent_marker(
[cordelia, lear, ali, alice, hal, gael, hamlet, othello, mention_all], [
cordelia_item,
lear_item,
ali_item,
alice_item,
hal_item,
gael_item,
hamlet_item,
othello_item,
mention_all,
],
false, false,
), ),
); );
assert_typeahead_equals( assert_typeahead_equals(
"test\n @_l", "test\n @_l",
mentions_with_silent_marker( mentions_with_silent_marker(
[cordelia, lear, ali, alice, hal, gael, hamlet, othello, hamletcharacters, call_center], [
cordelia_item,
lear_item,
ali_item,
alice_item,
hal_item,
gael_item,
hamlet_item,
othello_item,
hamletcharacters,
call_center,
],
true, true,
), ),
); );
@ -1583,9 +1635,12 @@ test("begins_typeahead", ({override, override_rewire}) => {
assert_typeahead_equals(" @_zuli", []); assert_typeahead_equals(" @_zuli", []);
assert_typeahead_equals( assert_typeahead_equals(
"test @o", "test @o",
mentions_with_silent_marker([othello, cordelia, mention_everyone], false), mentions_with_silent_marker([othello_item, cordelia_item, mention_everyone], false),
);
assert_typeahead_equals(
"test @_o",
mentions_with_silent_marker([othello_item, cordelia_item], true),
); );
assert_typeahead_equals("test @_o", mentions_with_silent_marker([othello, cordelia], true));
assert_typeahead_equals("test @z", []); assert_typeahead_equals("test @z", []);
assert_typeahead_equals("test @_z", []); assert_typeahead_equals("test @_z", []);
@ -1760,7 +1815,11 @@ test("begins_typeahead", ({override, override_rewire}) => {
assert_typeahead_equals("~~~test", "ing", []); assert_typeahead_equals("~~~test", "ing", []);
const terminal_symbols = ",.;?!()[]> \"'\n\t"; const terminal_symbols = ",.;?!()[]> \"'\n\t";
for (const symbol of terminal_symbols.split()) { for (const symbol of terminal_symbols.split()) {
assert_typeahead_equals("@othello", symbol, mentions_with_silent_marker([othello], false)); assert_typeahead_equals(
"@othello",
symbol,
mentions_with_silent_marker([othello_item], false),
);
assert_typeahead_equals(":tada", symbol, emoji_objects(["tada"])); assert_typeahead_equals(":tada", symbol, emoji_objects(["tada"]));
assert_typeahead_starts_with("```p", symbol, p_langs); assert_typeahead_starts_with("```p", symbol, p_langs);
assert_typeahead_starts_with("~~~p", symbol, p_langs); assert_typeahead_starts_with("~~~p", symbol, p_langs);
@ -1808,10 +1867,10 @@ test("content_highlighter_html", ({override_rewire}) => {
ct.get_or_set_completing_for_tests("mention"); ct.get_or_set_completing_for_tests("mention");
let th_render_person_called = false; let th_render_person_called = false;
override_rewire(typeahead_helper, "render_person", (person) => { override_rewire(typeahead_helper, "render_person", (person) => {
assert.deepEqual(person, othello); assert.deepEqual(person, othello_item);
th_render_person_called = true; th_render_person_called = true;
}); });
ct.content_highlighter_html(othello); ct.content_highlighter_html(othello_item);
let th_render_user_group_called = false; let th_render_user_group_called = false;
override_rewire(typeahead_helper, "render_user_group", (user_group) => { override_rewire(typeahead_helper, "render_user_group", (user_group) => {
@ -1873,10 +1932,10 @@ test("filter_and_sort_mentions (normal)", () => {
current_user.user_id = 101; current_user.user_id = 101;
let suggestions = ct.filter_and_sort_mentions(is_silent, "al"); let suggestions = ct.filter_and_sort_mentions(is_silent, "al");
const mention_all = user_or_mention_item(ct.broadcast_mentions()[0]); const mention_all = broadcast_item(ct.broadcast_mentions()[0]);
assert.deepEqual( assert.deepEqual(
suggestions, suggestions,
possibly_silent_list([mention_all, ali, alice, hal, call_center], is_silent), possibly_silent_list([mention_all, ali_item, alice_item, hal_item, call_center], is_silent),
); );
// call_center group is shown in typeahead even when user is member of // call_center group is shown in typeahead even when user is member of
@ -1885,7 +1944,7 @@ test("filter_and_sort_mentions (normal)", () => {
suggestions = ct.filter_and_sort_mentions(is_silent, "al"); suggestions = ct.filter_and_sort_mentions(is_silent, "al");
assert.deepEqual( assert.deepEqual(
suggestions, suggestions,
possibly_silent_list([mention_all, ali, alice, hal, call_center], is_silent), possibly_silent_list([mention_all, ali_item, alice_item, hal_item, call_center], is_silent),
); );
// call_center group is not shown in typeahead when user is neither // call_center group is not shown in typeahead when user is neither
@ -1893,7 +1952,10 @@ test("filter_and_sort_mentions (normal)", () => {
// recursive subgroups. // recursive subgroups.
current_user.user_id = 102; current_user.user_id = 102;
suggestions = ct.filter_and_sort_mentions(is_silent, "al"); suggestions = ct.filter_and_sort_mentions(is_silent, "al");
assert.deepEqual(suggestions, possibly_silent_list([mention_all, ali, alice, hal], is_silent)); assert.deepEqual(
suggestions,
possibly_silent_list([mention_all, ali_item, alice_item, hal_item], is_silent),
);
}); });
test("filter_and_sort_mentions (silent)", () => { test("filter_and_sort_mentions (silent)", () => {
@ -1901,14 +1963,20 @@ test("filter_and_sort_mentions (silent)", () => {
let suggestions = ct.filter_and_sort_mentions(is_silent, "al"); let suggestions = ct.filter_and_sort_mentions(is_silent, "al");
assert.deepEqual(suggestions, possibly_silent_list([ali, alice, hal, call_center], is_silent)); assert.deepEqual(
suggestions,
possibly_silent_list([ali_item, alice_item, hal_item, call_center], is_silent),
);
// call_center group is shown in typeahead irrespective of whether // call_center group is shown in typeahead irrespective of whether
// user is member of can_mention_group or its subgroups for a // user is member of can_mention_group or its subgroups for a
// silent mention. // silent mention.
current_user.user_id = 102; current_user.user_id = 102;
suggestions = ct.filter_and_sort_mentions(is_silent, "al"); suggestions = ct.filter_and_sort_mentions(is_silent, "al");
assert.deepEqual(suggestions, possibly_silent_list([ali, alice, hal, call_center], is_silent)); assert.deepEqual(
suggestions,
possibly_silent_list([ali_item, alice_item, hal_item, call_center], is_silent),
);
}); });
test("typeahead_results", () => { test("typeahead_results", () => {
@ -2005,22 +2073,22 @@ test("typeahead_results", () => {
is_silent: false, is_silent: false,
}; };
} }
assert_mentions_matches("cordelia", [not_silent(cordelia)]); assert_mentions_matches("cordelia", [not_silent(cordelia_item)]);
assert_mentions_matches("cordelia, le", [not_silent(cordelia)]); assert_mentions_matches("cordelia, le", [not_silent(cordelia_item)]);
assert_mentions_matches("cordelia, le ", []); assert_mentions_matches("cordelia, le ", []);
assert_mentions_matches("moor", [not_silent(othello)]); assert_mentions_matches("moor", [not_silent(othello_item)]);
assert_mentions_matches("moor ", [not_silent(othello)]); assert_mentions_matches("moor ", [not_silent(othello_item)]);
assert_mentions_matches("moor of", [not_silent(othello)]); assert_mentions_matches("moor of", [not_silent(othello_item)]);
assert_mentions_matches("moor of ven", [not_silent(othello)]); assert_mentions_matches("moor of ven", [not_silent(othello_item)]);
assert_mentions_matches("oor", [not_silent(othello)]); assert_mentions_matches("oor", [not_silent(othello_item)]);
assert_mentions_matches("oor ", []); assert_mentions_matches("oor ", []);
assert_mentions_matches("oor o", []); assert_mentions_matches("oor o", []);
assert_mentions_matches("oor of venice", []); assert_mentions_matches("oor of venice", []);
assert_mentions_matches("King ", [not_silent(hamlet), not_silent(lear)]); assert_mentions_matches("King ", [not_silent(hamlet_item), not_silent(lear_item)]);
assert_mentions_matches("King H", [not_silent(hamlet)]); assert_mentions_matches("King H", [not_silent(hamlet_item)]);
assert_mentions_matches("King L", [not_silent(lear)]); assert_mentions_matches("King L", [not_silent(lear_item)]);
assert_mentions_matches("delia lear", []); assert_mentions_matches("delia lear", []);
assert_mentions_matches("Mark Tw", [not_silent(twin1), not_silent(twin2)]); assert_mentions_matches("Mark Tw", [not_silent(twin1_item), not_silent(twin2_item)]);
// Earlier user group and stream mentions were autocompleted by their // Earlier user group and stream mentions were autocompleted by their
// description too. This is now removed as it often led to unexpected // description too. This is now removed as it often led to unexpected
@ -2034,31 +2102,31 @@ test("typeahead_results", () => {
// Verify we suggest only the first matching stream wildcard mention, // Verify we suggest only the first matching stream wildcard mention,
// irrespective of how many equivalent stream wildcard mentions match. // irrespective of how many equivalent stream wildcard mentions match.
const mention_everyone = not_silent(user_or_mention_item(ct.broadcast_mentions()[1])); const mention_everyone = not_silent(broadcast_item(ct.broadcast_mentions()[1]));
// Here, we suggest only "everyone" instead of both the matching // Here, we suggest only "everyone" instead of both the matching
// "everyone" and "stream" wildcard mentions. // "everyone" and "stream" wildcard mentions.
assert_mentions_matches("e", [ assert_mentions_matches("e", [
not_silent(mention_everyone), not_silent(mention_everyone),
not_silent(hal), not_silent(hal_item),
not_silent(alice), not_silent(alice_item),
not_silent(cordelia), not_silent(cordelia_item),
not_silent(gael), not_silent(gael_item),
not_silent(hamlet), not_silent(hamlet_item),
not_silent(lear), not_silent(lear_item),
not_silent(othello), not_silent(othello_item),
not_silent(hamletcharacters), not_silent(hamletcharacters),
not_silent(call_center), not_silent(call_center),
]); ]);
// Verify we suggest both 'the first matching stream wildcard' and // Verify we suggest both 'the first matching stream wildcard' and
// 'topic wildcard' mentions. Not only one matching wildcard mention. // 'topic wildcard' mentions. Not only one matching wildcard mention.
const mention_topic = user_or_mention_item(ct.broadcast_mentions()[4]); const mention_topic = broadcast_item(ct.broadcast_mentions()[4]);
// Here, we suggest both "everyone" and "topic". // Here, we suggest both "everyone" and "topic".
assert_mentions_matches("o", [ assert_mentions_matches("o", [
not_silent(othello), not_silent(othello_item),
not_silent(mention_everyone), not_silent(mention_everyone),
not_silent(mention_topic), not_silent(mention_topic),
not_silent(cordelia), not_silent(cordelia_item),
]); ]);
// Autocomplete by slash commands. // Autocomplete by slash commands.
@ -2101,20 +2169,20 @@ test("message people", ({override, override_rewire}) => {
}; };
results = ct.get_person_suggestions("Ha", opts); results = ct.get_person_suggestions("Ha", opts);
assert.deepEqual(results, [harry, hal]); assert.deepEqual(results, [harry_item, hal_item]);
// Now let's exclude Hal and include King Hamlet. // Now let's exclude Hal and include King Hamlet.
user_ids = [hamlet.user_id, harry.user_id]; user_ids = [hamlet.user_id, harry.user_id];
results = ct.get_person_suggestions("Ha", opts); results = ct.get_person_suggestions("Ha", opts);
assert.deepEqual(results, [harry, hamlet]); assert.deepEqual(results, [harry_item, hamlet_item]);
// Reincluding Hal and deactivating harry // Reincluding Hal and deactivating harry
user_ids = [hamlet.user_id, harry.user_id, hal.user_id]; user_ids = [hamlet.user_id, harry.user_id, hal.user_id];
people.deactivate(harry); people.deactivate(harry);
results = ct.get_person_suggestions("Ha", opts); results = ct.get_person_suggestions("Ha", opts);
// harry is excluded since it has been deactivated. // harry is excluded since it has been deactivated.
assert.deepEqual(results, [hal, hamlet]); assert.deepEqual(results, [hal_item, hamlet_item]);
}); });
test("muted users excluded from results", () => { test("muted users excluded from results", () => {
@ -2127,7 +2195,7 @@ test("muted users excluded from results", () => {
// Nobody is muted // Nobody is muted
results = ct.get_person_suggestions("corde", opts); results = ct.get_person_suggestions("corde", opts);
assert.deepEqual(results, [cordelia]); assert.deepEqual(results, [cordelia_item]);
// Mute Cordelia, and test that she's excluded from results. // Mute Cordelia, and test that she's excluded from results.
muted_users.add_muted_user(cordelia.user_id); muted_users.add_muted_user(cordelia.user_id);
@ -2137,7 +2205,7 @@ test("muted users excluded from results", () => {
// Make sure our muting logic doesn't break wildcard mentions // Make sure our muting logic doesn't break wildcard mentions
// or user group mentions. // or user group mentions.
results = ct.get_person_suggestions("all", opts); results = ct.get_person_suggestions("all", opts);
const mention_all = user_or_mention_item(ct.broadcast_mentions()[0]); const mention_all = broadcast_item(ct.broadcast_mentions()[0]);
assert.deepEqual(results, [mention_all, call_center]); assert.deepEqual(results, [mention_all, call_center]);
}); });
@ -2163,12 +2231,12 @@ test("direct message recipients sorted according to stream / topic being viewed"
compose_state.set_stream_id(""); compose_state.set_stream_id("");
results = ct.get_pm_people("li"); results = ct.get_pm_people("li");
// `get_pm_people` can't return mentions, so the items are all user items. // `get_pm_people` can't return mentions, so the items are all user items.
assert.deepEqual(results, [user_item(ali), user_item(alice), user_item(cordelia)]); assert.deepEqual(results, [ali_item, alice_item, cordelia_item]);
// When viewing denmark stream, subscriber cordelia is placed higher // When viewing denmark stream, subscriber cordelia is placed higher
compose_state.set_stream_id(denmark_stream.stream_id); compose_state.set_stream_id(denmark_stream.stream_id);
results = ct.get_pm_people("li"); results = ct.get_pm_people("li");
assert.deepEqual(results, [user_item(cordelia), user_item(ali), user_item(alice)]); assert.deepEqual(results, [cordelia_item, ali_item, alice_item]);
// Simulating just alice being subscribed to denmark. // Simulating just alice being subscribed to denmark.
override_rewire( override_rewire(
@ -2180,5 +2248,5 @@ test("direct message recipients sorted according to stream / topic being viewed"
// When viewing denmark stream to which alice is subscribed, ali is not // When viewing denmark stream to which alice is subscribed, ali is not
// 1st despite having an exact name match with the query. // 1st despite having an exact name match with the query.
results = ct.get_pm_people("ali"); results = ct.get_pm_people("ali");
assert.deepEqual(results, [user_item(alice), user_item(ali)]); assert.deepEqual(results, [alice_item, ali_item]);
}); });

View File

@ -39,10 +39,7 @@ function override_typeahead_helper(override_rewire) {
} }
function user_item(user) { function user_item(user) {
return { return {type: "user", user};
...user,
type: "user",
};
} }
const jill = { const jill = {

View File

@ -27,23 +27,17 @@ let next_id = 0;
function assertSameEmails(lst1, lst2) { function assertSameEmails(lst1, lst2) {
assert.deepEqual( assert.deepEqual(
lst1.map((r) => r.email), lst1.map((r) => r.user.email),
lst2.map((r) => r.email), lst2.map((r) => r.user.email),
); );
} }
function user_item(user) { function user_item(user) {
return { return {type: "user", user};
...user,
type: "user",
};
} }
function user_or_mention_item(user_or_mention) { function broadcast_item(user) {
return { return {type: "broadcast", user};
...user_or_mention,
type: "user_or_mention",
};
} }
const a_bot = { const a_bot = {
@ -53,6 +47,7 @@ const a_bot = {
is_bot: true, is_bot: true,
user_id: 1, user_id: 1,
}; };
const a_bot_item = user_item(a_bot);
const a_user = { const a_user = {
email: "a_user@zulip.org", email: "a_user@zulip.org",
@ -61,6 +56,7 @@ const a_user = {
is_bot: false, is_bot: false,
user_id: 2, user_id: 2,
}; };
const a_user_item = user_item(a_user);
const b_user_1 = { const b_user_1 = {
email: "b_user_1@zulip.net", email: "b_user_1@zulip.net",
@ -78,6 +74,7 @@ const b_user_2 = {
is_bot: false, is_bot: false,
user_id: 4, user_id: 4,
}; };
const b_user_2_item = user_item(b_user_2);
const b_user_3 = { const b_user_3 = {
email: "b_user_3@zulip.net", email: "b_user_3@zulip.net",
@ -95,6 +92,7 @@ const b_bot = {
is_bot: true, is_bot: true,
user_id: 6, user_id: 6,
}; };
const b_bot_item = user_item(b_bot);
const zman = { const zman = {
email: "zman@test.net", email: "zman@test.net",
@ -361,17 +359,14 @@ test("sort_languages on actual data", () => {
}); });
function get_typeahead_result(query, current_stream_id, current_topic) { function get_typeahead_result(query, current_stream_id, current_topic) {
const users = people.get_realm_users().map((user) => ({ const users = people.get_realm_users().map((user) => ({type: "user", user}));
...user,
type: "user",
}));
const result = th.sort_recipients({ const result = th.sort_recipients({
users, users,
query, query,
current_stream_id, current_stream_id,
current_topic, current_topic,
}); });
return result.map((person) => person.email); return result.map((person) => person.user.email);
} }
test("sort_recipients", () => { test("sort_recipients", () => {
@ -470,14 +465,13 @@ test("sort_recipients all mention", () => {
compose_state.set_message_type("stream"); compose_state.set_message_type("stream");
const all_obj = ct.broadcast_mentions()[0]; const all_obj = ct.broadcast_mentions()[0];
assert.equal(all_obj.email, "all"); assert.equal(all_obj.email, "all");
assert.equal(all_obj.is_broadcast, true);
assert.equal(all_obj.idx, 0); assert.equal(all_obj.idx, 0);
// Test person email is "all" or "everyone" // Test person email is "all" or "everyone"
const test_objs = [...matches, all_obj]; const user_and_mention_items = [
const user_and_mention_items = test_objs.map((user_or_mention) => ...matches.map((user) => user_item(user)),
user_or_mention_item(user_or_mention), broadcast_item(all_obj),
); ];
const results = th.sort_recipients({ const results = th.sort_recipients({
users: user_and_mention_items, users: user_and_mention_items,
query: "a", query: "a",
@ -485,7 +479,16 @@ test("sort_recipients all mention", () => {
current_topic: "Linux topic", current_topic: "Linux topic",
}); });
assertSameEmails(results, [all_obj, a_user, a_bot, b_user_1, b_user_2, b_user_3, zman, b_bot]); assertSameEmails(results, [
broadcast_item(all_obj),
a_user_item,
a_bot_item,
b_user_1_item,
b_user_2_item,
b_user_3_item,
zman_item,
b_bot_item,
]);
}); });
test("sort_recipients pm counts", () => { test("sort_recipients pm counts", () => {
@ -538,17 +541,14 @@ test("sort_recipients pm counts", () => {
test("sort_recipients dup bots", () => { test("sort_recipients dup bots", () => {
const dup_objects = [...matches, a_bot]; const dup_objects = [...matches, a_bot];
const user_items = dup_objects.map((user) => ({ const user_items = dup_objects.map((user) => user_item(user));
...user,
type: "user",
}));
const recipients = th.sort_recipients({ const recipients = th.sort_recipients({
users: user_items, users: user_items,
query: "b", query: "b",
current_stream_id: undefined, current_stream_id: undefined,
current_topic: "", current_topic: "",
}); });
const recipients_email = recipients.map((person) => person.email); const recipients_email = recipients.map((person) => person.user.email);
const expected = [ const expected = [
"b_user_1@zulip.net", "b_user_1@zulip.net",
"b_user_2@zulip.net", "b_user_2@zulip.net",
@ -564,13 +564,10 @@ test("sort_recipients dup bots", () => {
test("sort_recipients dup alls", () => { test("sort_recipients dup alls", () => {
compose_state.set_message_type("stream"); compose_state.set_message_type("stream");
const all_obj = ct.broadcast_mentions()[0]; const all_obj_item = broadcast_item(ct.broadcast_mentions()[0]);
// full_name starts with same character but emails are 'all' // full_name starts with same character but emails are 'all'
const test_objs = [all_obj, a_user, all_obj]; const user_and_mention_items = [all_obj_item, a_user_item, all_obj_item];
const user_and_mention_items = test_objs.map((user_or_mention) =>
user_or_mention_item(user_or_mention),
);
const recipients = th.sort_recipients({ const recipients = th.sort_recipients({
users: user_and_mention_items, users: user_and_mention_items,
query: "a", query: "a",
@ -578,43 +575,36 @@ test("sort_recipients dup alls", () => {
current_topic: "Linux topic", current_topic: "Linux topic",
}); });
const expected = [all_obj, a_user]; const expected = [all_obj_item, a_user_item];
assertSameEmails(recipients, expected); assertSameEmails(recipients, expected);
}); });
test("sort_recipients dup alls direct message", () => { test("sort_recipients dup alls direct message", () => {
compose_state.set_message_type("private"); compose_state.set_message_type("private");
const all_obj = ct.broadcast_mentions()[0]; const all_obj_item = broadcast_item(ct.broadcast_mentions()[0]);
// full_name starts with same character but emails are 'all' // full_name starts with same character but emails are 'all'
const test_objs = [all_obj, a_user, all_obj]; const user_and_mention_items = [all_obj_item, a_user_item, all_obj_item];
const user_and_mention_items = test_objs.map((user_or_mention) =>
user_or_mention_item(user_or_mention),
);
const recipients = th.sort_recipients({ const recipients = th.sort_recipients({
users: user_and_mention_items, users: user_and_mention_items,
query: "a", query: "a",
}); });
const expected = [a_user, all_obj]; const expected = [a_user_item, all_obj_item];
assertSameEmails(recipients, expected); assertSameEmails(recipients, expected);
}); });
test("sort_recipients subscribers", () => { test("sort_recipients subscribers", () => {
// b_user_2 is a subscriber and b_user_1 is not. // b_user_2 is a subscriber and b_user_1 is not.
peer_data.add_subscriber(dev_sub.stream_id, b_user_2.user_id); peer_data.add_subscriber(dev_sub.stream_id, b_user_2.user_id);
const small_matches = [b_user_2, b_user_1]; const user_items = [b_user_2_item, b_user_1_item];
const user_items = small_matches.map((user) => ({
...user,
type: "user",
}));
const recipients = th.sort_recipients({ const recipients = th.sort_recipients({
users: user_items, users: user_items,
query: "b", query: "b",
current_stream_id: dev_sub.stream_id, current_stream_id: dev_sub.stream_id,
current_topic: "Dev topic", current_topic: "Dev topic",
}); });
const recipients_email = recipients.map((person) => person.email); const recipients_email = recipients.map((person) => person.user.email);
const expected = ["b_user_2@zulip.net", "b_user_1@zulip.net"]; const expected = ["b_user_2@zulip.net", "b_user_1@zulip.net"];
assert.deepEqual(recipients_email, expected); assert.deepEqual(recipients_email, expected);
}); });
@ -622,7 +612,6 @@ test("sort_recipients subscribers", () => {
test("sort_recipients recent senders", () => { test("sort_recipients recent senders", () => {
// b_user_2 is the only recent sender, b_user_3 is the only pm partner // b_user_2 is the only recent sender, b_user_3 is the only pm partner
// and all are subscribed to the stream Linux. // and all are subscribed to the stream Linux.
const small_matches = [b_user_1, b_user_2, b_user_3];
peer_data.add_subscriber(linux_sub.stream_id, b_user_1.user_id); peer_data.add_subscriber(linux_sub.stream_id, b_user_1.user_id);
peer_data.add_subscriber(linux_sub.stream_id, b_user_2.user_id); peer_data.add_subscriber(linux_sub.stream_id, b_user_2.user_id);
peer_data.add_subscriber(linux_sub.stream_id, b_user_3.user_id); peer_data.add_subscriber(linux_sub.stream_id, b_user_3.user_id);
@ -633,17 +622,14 @@ test("sort_recipients recent senders", () => {
id: (next_id += 1), id: (next_id += 1),
}); });
pm_conversations.set_partner(b_user_3.user_id); pm_conversations.set_partner(b_user_3.user_id);
const user_items = small_matches.map((user) => ({ const user_items = [b_user_1_item, b_user_2_item, b_user_3_item];
...user,
type: "user",
}));
const recipients = th.sort_recipients({ const recipients = th.sort_recipients({
users: user_items, users: user_items,
query: "b", query: "b",
current_stream_id: linux_sub.stream_id, current_stream_id: linux_sub.stream_id,
current_topic: "Linux topic", current_topic: "Linux topic",
}); });
const recipients_email = recipients.map((person) => person.email); const recipients_email = recipients.map((person) => person.user.email);
// Prefer recent sender over pm partner // Prefer recent sender over pm partner
const expected = ["b_user_2@zulip.net", "b_user_3@zulip.net", "b_user_1@zulip.net"]; const expected = ["b_user_2@zulip.net", "b_user_3@zulip.net", "b_user_1@zulip.net"];
assert.deepEqual(recipients_email, expected); assert.deepEqual(recipients_email, expected);
@ -653,18 +639,14 @@ test("sort_recipients pm partners", () => {
// b_user_3 is a pm partner and b_user_2 is not and // b_user_3 is a pm partner and b_user_2 is not and
// both are not subscribed to the stream Linux. // both are not subscribed to the stream Linux.
pm_conversations.set_partner(b_user_3.user_id); pm_conversations.set_partner(b_user_3.user_id);
const small_matches = [b_user_3, b_user_2]; const user_items = [b_user_3_item, b_user_2_item];
const user_items = small_matches.map((user) => ({
...user,
type: "user",
}));
const recipients = th.sort_recipients({ const recipients = th.sort_recipients({
users: user_items, users: user_items,
query: "b", query: "b",
current_stream_id: linux_sub.stream_id, current_stream_id: linux_sub.stream_id,
current_topic: "Linux topic", current_topic: "Linux topic",
}); });
const recipients_email = recipients.map((person) => person.email); const recipients_email = recipients.map((person) => person.user.email);
const expected = ["b_user_3@zulip.net", "b_user_2@zulip.net"]; const expected = ["b_user_3@zulip.net", "b_user_2@zulip.net"];
assert.deepEqual(recipients_email, expected); assert.deepEqual(recipients_email, expected);
}); });
@ -676,27 +658,29 @@ test("sort broadcast mentions for stream message type", () => {
// randomly rearrange them) // randomly rearrange them)
compose_state.set_message_type("stream"); compose_state.set_message_type("stream");
const mentions = ct.broadcast_mentions().reverse(); const mentions = ct.broadcast_mentions().reverse();
const mention_items = mentions.map((mention) => user_or_mention_item(mention)); const broadcast_items = mentions.map((broadcast) => broadcast_item(broadcast));
const results = th.sort_people_for_relevance(mention_items, "", ""); const results = th.sort_people_for_relevance(broadcast_items, "", "");
assert.deepEqual( assert.deepEqual(
results.map((r) => r.email), results.map((r) => r.user.email),
["all", "everyone", "stream", "channel", "topic"], ["all", "everyone", "stream", "channel", "topic"],
); );
// Reverse the list to test actual sorting // Reverse the list to test actual sorting
// and ensure test coverage for the defensive // and ensure test coverage for the defensive
// code. Also, add in some people users. // code. Also, add in some people users.
const test_objs = [...ct.broadcast_mentions()].reverse(); const user_or_mention_items = [
test_objs.unshift(zman); zman_item,
test_objs.push(a_user); ...ct
const user_or_mention_items = test_objs.map((user_or_mention) => .broadcast_mentions()
user_or_mention_item(user_or_mention), .map((broadcast) => broadcast_item(broadcast))
); .reverse(),
a_user_item,
];
const results2 = th.sort_people_for_relevance(user_or_mention_items, "", ""); const results2 = th.sort_people_for_relevance(user_or_mention_items, "", "");
assert.deepEqual( assert.deepEqual(
results2.map((r) => r.email), results2.map((r) => r.user.email),
["all", "everyone", "stream", "channel", "topic", a_user.email, zman.email], ["all", "everyone", "stream", "channel", "topic", a_user.email, zman.email],
); );
}); });
@ -704,24 +688,26 @@ test("sort broadcast mentions for stream message type", () => {
test("sort broadcast mentions for direct message type", () => { test("sort broadcast mentions for direct message type", () => {
compose_state.set_message_type("private"); compose_state.set_message_type("private");
const mentions = ct.broadcast_mentions().reverse(); const mentions = ct.broadcast_mentions().reverse();
const mention_items = mentions.map((mention) => user_or_mention_item(mention)); const broadcast_items = mentions.map((broadcast) => broadcast_item(broadcast));
const results = th.sort_people_for_relevance(mention_items, "", ""); const results = th.sort_people_for_relevance(broadcast_items, "", "");
assert.deepEqual( assert.deepEqual(
results.map((r) => r.email), results.map((r) => r.user.email),
["all", "everyone"], ["all", "everyone"],
); );
const test_objs = [...ct.broadcast_mentions()].reverse(); const user_or_mention_items = [
test_objs.unshift(zman); zman_item,
test_objs.push(a_user); ...ct
const user_or_mention_items = test_objs.map((user_or_mention) => .broadcast_mentions()
user_or_mention_item(user_or_mention), .map((broadcast) => broadcast_item(broadcast))
); .reverse(),
a_user_item,
];
const results2 = th.sort_people_for_relevance(user_or_mention_items, "", ""); const results2 = th.sort_people_for_relevance(user_or_mention_items, "", "");
assert.deepEqual( assert.deepEqual(
results2.map((r) => r.email), results2.map((r) => r.user.email),
[a_user.email, zman.email, "all", "everyone"], [a_user.email, zman.email, "all", "everyone"],
); );
}); });
@ -732,7 +718,7 @@ test("test compare directly for stream message type", () => {
// coverage is subject to the whims of how JS sorts. // coverage is subject to the whims of how JS sorts.
compose_state.set_message_type("stream"); compose_state.set_message_type("stream");
const all_obj = ct.broadcast_mentions()[0]; const all_obj = ct.broadcast_mentions()[0];
const all_obj_item = user_or_mention_item(all_obj); const all_obj_item = broadcast_item(all_obj);
assert.equal(th.compare_people_for_relevance(all_obj_item, all_obj_item), 0); assert.equal(th.compare_people_for_relevance(all_obj_item, all_obj_item), 0);
assert.equal(th.compare_people_for_relevance(all_obj_item, zman_item), -1); assert.equal(th.compare_people_for_relevance(all_obj_item, zman_item), -1);
@ -742,7 +728,7 @@ test("test compare directly for stream message type", () => {
test("test compare directly for direct message", () => { test("test compare directly for direct message", () => {
compose_state.set_message_type("private"); compose_state.set_message_type("private");
const all_obj = ct.broadcast_mentions()[0]; const all_obj = ct.broadcast_mentions()[0];
const all_obj_item = user_or_mention_item(all_obj); const all_obj_item = broadcast_item(all_obj);
assert.equal(th.compare_people_for_relevance(all_obj_item, all_obj_item), 0); assert.equal(th.compare_people_for_relevance(all_obj_item, all_obj_item), 0);
assert.equal(th.compare_people_for_relevance(all_obj_item, zman_item), 1); assert.equal(th.compare_people_for_relevance(all_obj_item, zman_item), 1);
@ -791,7 +777,7 @@ test("render_person when emails hidden", ({mock_template}) => {
rendered = true; rendered = true;
return "typeahead-item-stub"; return "typeahead-item-stub";
}); });
assert.equal(th.render_person(b_user_1), "typeahead-item-stub"); assert.equal(th.render_person(b_user_1_item), "typeahead-item-stub");
assert.ok(rendered); assert.ok(rendered);
}); });
@ -808,7 +794,7 @@ test("render_person", ({mock_template}) => {
rendered = true; rendered = true;
return "typeahead-item-stub"; return "typeahead-item-stub";
}); });
assert.equal(th.render_person(a_user), "typeahead-item-stub"); assert.equal(th.render_person(a_user_item), "typeahead-item-stub");
assert.ok(rendered); assert.ok(rendered);
}); });
@ -823,8 +809,6 @@ test("render_person special_item_text", ({mock_template}) => {
is_bot: false, is_bot: false,
user_id: 7, user_id: 7,
special_item_text: "special_text", special_item_text: "special_text",
is_broadcast: true,
type: "user_or_mention",
}; };
rendered = false; rendered = false;
@ -833,7 +817,7 @@ test("render_person special_item_text", ({mock_template}) => {
rendered = true; rendered = true;
return "typeahead-item-stub"; return "typeahead-item-stub";
}); });
assert.equal(th.render_person(special_person), "typeahead-item-stub"); assert.equal(th.render_person(broadcast_item(special_person)), "typeahead-item-stub");
assert.ok(rendered); assert.ok(rendered);
}); });

View File

@ -147,10 +147,5 @@ test("typeahead", () => {
// excluded by virtue of already being one of the widget items. // excluded by virtue of already being one of the widget items.
// And then bogus_item is just a red herring to test robustness. // And then bogus_item is just a red herring to test robustness.
const result = user_pill.typeahead_source(pill_widget); const result = user_pill.typeahead_source(pill_widget);
assert.deepEqual(result, [ assert.deepEqual(result, [{type: "user", user: alice}]);
{
...alice,
type: "user",
},
]);
}); });