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;
}
if (mentioned.is_broadcast) {
if (mentioned.type === "broadcast") {
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);
if (!stream_id) {
@ -237,7 +237,7 @@ export function warn_if_mentioning_unsubscribed_user(
? $t({defaultMessage: "Subscribe them"})
: null,
can_subscribe_other_users,
name: mentioned.full_name,
name: mentioned.user.full_name,
classname: compose_banner.CLASSNAMES.recipient_not_subscribed,
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 timerender from "./timerender";
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 * as user_groups from "./user_groups";
import type {UserGroup} from "./user_groups";
@ -493,7 +493,6 @@ export function broadcast_mentions(): PseudoMentionUser[] {
pm_recipient_count: Number.POSITIVE_INFINITY,
full_name: mention,
is_broadcast: true,
// used for sorting
idx,
@ -596,16 +595,9 @@ export function get_pm_people(query: string): (UserGroupPillData | UserPillData)
// to do this.
const user_suggestions: (UserGroupPillData | UserPillData)[] = [];
for (const suggestion of suggestions) {
if (suggestion.type === "user_or_mention") {
assert(suggestion.is_broadcast === undefined);
user_suggestions.push({
...suggestion,
type: "user",
});
} else {
assert(suggestion.type !== "broadcast");
user_suggestions.push(suggestion);
}
}
return user_suggestions;
}
@ -634,18 +626,20 @@ export function get_person_suggestions(
}
// Exclude muted users from typeaheads.
persons = muted_users.filter_muted_users(persons);
let user_or_mention_list: UserOrMention[] = persons.map((person) => ({
...person,
is_broadcast: undefined,
let person_items: UserOrMentionPillData[] = persons.map((person) => ({
type: "user",
user: person,
}));
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));
}
@ -975,7 +969,8 @@ export function content_highlighter_html(item: TypeaheadSuggestion): string | un
case "emoji":
return typeahead_helper.render_emoji(item);
case "user_group":
case "user_or_mention":
case "user":
case "broadcast":
return typeahead_helper.render_person_or_user_group(item);
case "slash":
return typeahead_helper.render_typeahead_item({
@ -1041,7 +1036,8 @@ export function content_typeahead_selected(
}
break;
case "user_group":
case "user_or_mention": {
case "user":
case "broadcast": {
const is_silent = item.is_silent;
beginning = beginning.slice(0, -token.length - 1);
if (beginning.endsWith("@_*")) {
@ -1061,14 +1057,18 @@ export function content_typeahead_selected(
// that functionality yet, and we haven't gotten much
// feedback on this being an actual pitfall.
} else {
const user_id = item.is_broadcast ? undefined : item.user_id;
let mention_text = people.get_mention_syntax(item.full_name, user_id, is_silent);
if (!is_silent && !item.is_broadcast) {
const user_id = item.type === "broadcast" ? undefined : item.user.user_id;
let mention_text = people.get_mention_syntax(
item.user.full_name,
user_id,
is_silent,
);
if (!is_silent && item.type !== "broadcast") {
compose_validate.warn_if_mentioning_unsubscribed_user(item, $textbox);
mention_text = compose_validate.convert_mentions_to_silent_in_direct_messages(
mention_text,
item.full_name,
item.user_id,
item.user.full_name,
item.user.user_id,
);
}
beginning += mention_text + " ";
@ -1358,7 +1358,7 @@ export function initialize({
pill_widget.clear_text();
}
} 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

View File

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

View File

@ -16,7 +16,8 @@ import type {UserPillData} from "./user_pill";
function person_matcher(query: string, item: UserPillData): boolean {
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
// is given priority. Otherwise we use
// default user_pill.typeahead_source.
const users: UserPillData[] = opts.user_source().map((user) => ({
...user,
type: "user",
}));
const users: UserPillData[] = opts
.user_source()
.map((user) => ({type: "user", user}));
source = [...source, ...users];
} else {
source = [...source, ...user_pill.typeahead_source(pills, exclude_bots)];
@ -132,7 +132,7 @@ export function set_up(
const users: UserPillData[] = [];
if (include_users) {
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);
}
}
@ -164,9 +164,9 @@ export function set_up(
} else if (
include_users &&
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");

View File

@ -26,9 +26,10 @@ import * as user_status from "./user_status";
import type {UserStatusEmojiInfo} from "./user_status";
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 & {
type: "user_or_mention";
is_silent?: boolean;
};
@ -116,32 +117,34 @@ export function render_typeahead_item(args: {
}
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({
primary: person.special_item_text,
primary: person.user.special_item_text,
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_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 typeahead_arguments = {
primary: person.full_name,
primary: person.user.full_name,
img_src: avatar_url,
user_circle_class,
is_person: true,
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,
secondary: person.delivery_email,
secondary: person.user.delivery_email,
};
return render_typeahead_item(typeahead_arguments);
@ -249,43 +252,31 @@ export function compare_people_for_relevance(
current_stream_id?: number,
): number {
// 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 (person_a_is_broadcast) {
if (person_b_is_broadcast) {
return person_a.idx - person_b.idx;
if (person_a.type === "broadcast") {
if (person_b.type === "broadcast") {
return person_a.user.idx - person_b.user.idx;
}
return -1;
} else if (person_b_is_broadcast) {
} else if (person_b.type === "broadcast") {
return 1;
}
} else {
if (person_a_is_broadcast) {
if (person_b_is_broadcast) {
return person_a.idx - person_b.idx;
if (person_a.type === "broadcast") {
if (person_b.type === "broadcast") {
return person_a.user.idx - person_b.user.idx;
}
return 1;
} else if (person_b_is_broadcast) {
} else if (person_b.type === "broadcast") {
return -1;
}
}
// 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
if (current_stream_id !== undefined) {
const a_is_sub = stream_data.is_user_subscribed(current_stream_id, person_a.user_id);
const b_is_sub = stream_data.is_user_subscribed(current_stream_id, person_b.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.user_id);
if (a_is_sub && !b_is_sub) {
return -1;
@ -295,13 +286,13 @@ export function compare_people_for_relevance(
}
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) {
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>(
@ -444,7 +435,7 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
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 = [
...users_name_results.exact_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(
query,
users_name_results.no_matches,
(p) => p.email,
(p) => p.user.email,
);
const email_good_matches = [
...email_results.exact_matches,
@ -523,11 +514,13 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
function add_user_recipients(items: UserType[]): void {
for (const item of items) {
const item_is_broadcast = item.type === "user_or_mention" && item.is_broadcast;
const topic_wildcard_mention = item.email === "topic";
if (!item_is_broadcast || topic_wildcard_mention || !stream_wildcard_mention_included) {
if (
item.type !== "broadcast" ||
item.user.email === "topic" ||
!stream_wildcard_mention_included
) {
recipients.push(item);
if (item_is_broadcast && !topic_wildcard_mention) {
if (item.type === "broadcast" && item.user.email !== "topic") {
stream_wildcard_mention_included = true;
}
}
@ -654,16 +647,13 @@ export function query_matches_person(
query: string,
person: UserPillData | UserOrMentionPillData,
): 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;
}
if (
(person.type === "user" || person.is_broadcast === undefined) &&
Boolean(person.delivery_email)
) {
if (person.type === "user" && Boolean(person.user.delivery_email)) {
return typeahead.query_matches_string_in_order(
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 UserPillData = User & {type: "user"};
export type UserPillData = {type: "user"; user: User};
export function create_item_from_email(
email: string,
@ -136,10 +136,7 @@ export function typeahead_source(
exclude_bots?: boolean,
): UserPillData[] {
const users = exclude_bots ? people.get_realm_active_human_users() : people.get_realm_users();
return filter_taken_users(users, pill_widget).map((user) => ({
...user,
type: "user",
}));
return filter_taken_users(users, pill_widget).map((user) => ({type: "user", user}));
}
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);
let mentioned_details = {
user: {
email: "foo@bar.com",
},
};
let new_banner_rendered = false;
@ -679,19 +681,19 @@ test_ui("warn_if_mentioning_unsubscribed_user", ({override, mock_template}) => {
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;
const msg_type = is_private ? "private" : "stream";
compose_state.set_message_type(msg_type);
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);
assert.ok(!new_banner_rendered);
}
test_noop_case(true, false, false);
test_noop_case(false, true, false);
test_noop_case(false, false, true);
test_noop_case(true, false, "user");
test_noop_case(false, true, "user");
test_noop_case(false, false, "broadcast");
$("#compose_invite_users").hide();
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.
mentioned_details = {
type: "user",
user: {
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;
const $banner_container = $("#compose_banners");

View File

@ -61,20 +61,12 @@ const ct = composebox_typeahead;
// broadcast-mentions/persons/groups.
ct.__Rewire__("max_num_items", 15);
function user_or_mention_item(item) {
return {
is_broadcast: undefined, // default, overridden by `item`
...item,
type: "user_or_mention",
};
function user_item(user) {
return {type: "user", user};
}
function user_item(item) {
return {
...item,
is_broadcast: undefined,
type: "user",
};
function broadcast_item(user) {
return {type: "broadcast", user};
}
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));
}
const ali = user_or_mention_item({
const ali = {
email: "ali@zulip.com",
user_id: 98,
full_name: "Ali",
is_moderator: false,
});
};
const ali_item = user_item(ali);
const alice = user_or_mention_item({
const alice = {
email: "alice@zulip.com",
user_id: 99,
full_name: "Alice",
is_moderator: false,
});
};
const alice_item = user_item(alice);
const hamlet = user_or_mention_item({
const hamlet = {
email: "hamlet@zulip.com",
user_id: 100,
full_name: "King Hamlet",
is_moderator: false,
});
};
const hamlet_item = user_item(hamlet);
const othello = user_or_mention_item({
const othello = {
email: "othello@zulip.com",
user_id: 101,
full_name: "Othello, the Moor of Venice",
is_moderator: false,
delivery_email: null,
});
};
const othello_item = user_item(othello);
const cordelia = user_or_mention_item({
const cordelia = {
email: "cordelia@zulip.com",
user_id: 102,
full_name: "Cordelia, Lear's daughter",
is_moderator: false,
});
};
const cordelia_item = user_item(cordelia);
const deactivated_user = user_or_mention_item({
const deactivated_user = {
email: "other@zulip.com",
user_id: 103,
full_name: "Deactivated User",
is_moderator: false,
});
};
const deactivated_user_item = user_item(deactivated_user);
const lear = user_or_mention_item({
const lear = {
email: "lear@zulip.com",
user_id: 104,
full_name: "King Lear",
is_moderator: false,
});
};
const lear_item = user_item(lear);
const twin1 = user_or_mention_item({
const twin1 = {
full_name: "Mark Twin",
is_moderator: false,
user_id: 105,
email: "twin1@zulip.com",
});
};
const twin1_item = user_item(twin1);
const twin2 = user_or_mention_item({
const twin2 = {
full_name: "Mark Twin",
is_moderator: false,
user_id: 106,
email: "twin2@zulip.com",
});
};
const twin2_item = user_item(twin2);
const gael = user_or_mention_item({
const gael = {
full_name: "Gaël Twin",
is_moderator: false,
user_id: 107,
email: "twin3@zulip.com",
});
};
const gael_item = user_item(gael);
const hal = user_or_mention_item({
const hal = {
full_name: "Earl Hal",
is_moderator: false,
user_id: 108,
email: "hal@zulip.com",
});
};
const hal_item = user_item(hal);
const harry = user_or_mention_item({
const harry = {
full_name: "Harry",
is_moderator: false,
user_id: 109,
email: "harry@zulip.com",
});
};
const harry_item = user_item(harry);
const hamletcharacters = user_group_item({
name: "hamletcharacters",
@ -457,17 +461,17 @@ const make_emoji = (emoji_dict) => ({
// Sorted by name
const sorted_user_list = [
ali,
alice,
cordelia,
hal, // Early Hal
gael,
harry,
hamlet, // King Hamlet
lear,
twin1, // Mark Twin
twin2,
othello,
ali_item,
alice_item,
cordelia_item,
hal_item, // Early Hal
gael_item,
harry_item,
hamlet_item, // King Hamlet
lear_item,
twin1_item, // Mark Twin
twin2_item,
othello_item,
];
function test(label, f) {
@ -585,38 +589,38 @@ test("content_typeahead_selected", ({override}) => {
query = "@**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** ";
assert.equal(actual_value, expected_value);
let warned_for_mention = false;
override(compose_validate, "warn_if_mentioning_unsubscribed_user", (mentioned) => {
assert.equal(mentioned, othello);
assert.equal(mentioned, othello_item);
warned_for_mention = true;
});
query = "@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** ";
assert.equal(actual_value, expected_value);
assert.ok(warned_for_mention);
query = "Hello @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** ";
assert.equal(actual_value, expected_value);
query = "@**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** ";
assert.equal(actual_value, expected_value);
query = "@*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** ";
assert.equal(actual_value, expected_value);
@ -638,7 +642,7 @@ test("content_typeahead_selected", ({override}) => {
// silent mention
ct.get_or_set_completing_for_tests("silent_mention");
const silent_hamlet = {
...hamlet,
...hamlet_item,
is_silent: true,
};
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.
let actual_value = options.source("");
let expected_value = [
user_item(ali),
user_item(alice),
user_item(cordelia),
user_item(hal),
user_item(gael),
user_item(harry),
user_item(hamlet),
user_item(lear),
user_item(twin1),
user_item(twin2),
user_item(othello),
ali_item,
alice_item,
cordelia_item,
hal_item,
gael_item,
harry_item,
hamlet_item,
lear_item,
twin1_item,
twin2_item,
othello_item,
hamletcharacters,
backend,
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);
function matcher(query, person) {
@ -970,46 +972,46 @@ test("initialize", ({override, override_rewire, mock_template}) => {
let query;
query = "el"; // Matches both "othELlo" and "cordELia"
assert.equal(matcher(query, othello), true);
assert.equal(matcher(query, cordelia), true);
assert.equal(matcher(query, othello_item), true);
assert.equal(matcher(query, cordelia_item), true);
query = "bender"; // Doesn't exist
assert.equal(matcher(query, othello), false);
assert.equal(matcher(query, cordelia), false);
assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia_item), false);
query = "gael";
assert.equal(matcher(query, gael), true);
assert.equal(matcher(query, gael_item), true);
query = "Gaël";
assert.equal(matcher(query, gael), true);
assert.equal(matcher(query, gael_item), true);
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
// (we're between typing names).
query = "othello@zulip.com, ";
assert.equal(matcher(query, othello), false);
assert.equal(matcher(query, cordelia), false);
assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia_item), false);
// query = 'othello@zulip.com,, , cord';
query = "cord";
assert.equal(matcher(query, othello), false);
assert.equal(matcher(query, cordelia), true);
assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia_item), true);
// If the user is already in the list, typeahead doesn't include it
// again.
query = "cordelia@zulip.com, cord";
assert.equal(matcher(query, othello), false);
assert.equal(matcher(query, cordelia), false);
assert.equal(matcher(query, othello_item), false);
assert.equal(matcher(query, cordelia_item), false);
// Matching by email
query = "oth";
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";
assert.equal(matcher(query, deactivated_user), true);
assert.equal(matcher(query, deactivated_user_item), true);
function sorter(query, people) {
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
// the input.
query = "othello";
actual_value = sorter(query, [othello]);
expected_value = [othello];
actual_value = sorter(query, [othello_item]);
expected_value = [othello_item];
assert.deepEqual(actual_value, expected_value);
query = "Ali";
actual_value = sorter(query, [alice, ali]);
expected_value = [ali, alice];
actual_value = sorter(query, [alice_item, ali_item]);
expected_value = [ali_item, alice_item];
assert.deepEqual(actual_value, expected_value);
// A literal match at the beginning of an element puts it at the top.
query = "co"; // Matches everything ("x@zulip.COm")
actual_value = sorter(query, [othello, deactivated_user, cordelia]);
expected_value = [cordelia, deactivated_user, othello];
actual_value.sort((a, b) => a.user_id - b.user_id);
expected_value.sort((a, b) => a.user_id - b.user_id);
actual_value = sorter(query, [othello_item, deactivated_user_item, cordelia_item]);
expected_value = [cordelia_item, deactivated_user_item, othello_item];
actual_value.sort((a, b) => a.user.user_id - b.user.user_id);
expected_value.sort((a, b) => a.user.user_id - b.user.user_id);
assert.deepEqual(actual_value, expected_value);
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`
// with normal space.
query = "cordelia, lear's\u00A0";
assert.equal(matcher(query, cordelia), true);
assert.equal(matcher(query, othello), false);
assert.equal(matcher(query, cordelia_item), true);
assert.equal(matcher(query, othello_item), false);
const event = {
target: "#doesnotmatter",
@ -1060,12 +1062,12 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// options.updater()
options.query = "othello";
appended_names = [];
options.updater(othello, event);
options.updater(othello_item, event);
assert.deepEqual(appended_names, ["Othello, the Moor of Venice"]);
options.query = "othello@zulip.com, cor";
appended_names = [];
actual_value = options.updater(cordelia, event);
actual_value = options.updater(cordelia_item, event);
assert.deepEqual(appended_names, ["Cordelia, Lear's daughter"]);
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)
$("#private_message_recipient").trigger("blur");
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"]);
cleared = false;
@ -1120,7 +1122,7 @@ test("initialize", ({override, override_rewire, mock_template}) => {
// content_highlighter_html.
ct.get_or_set_completing_for_tests("mention");
ct.get_or_set_token_for_testing("othello");
actual_value = options.highlighter_html(othello);
actual_value = options.highlighter_html(othello_item);
expected_value =
` <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` +
@ -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_user_groups = [
...sorted_user_list,
@ -1495,7 +1497,7 @@ test("begins_typeahead", ({override, override_rewire}) => {
backend,
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) {
return mentions.map((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(
"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 @*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 @_*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(
"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(
"test @_*h",
mentions_with_silent_marker(
[harry, hal, hamlet, hamletcharacters, cordelia, othello],
[harry_item, hal_item, hamlet_item, hamletcharacters, cordelia_item, othello_item],
true,
),
);
@ -1550,28 +1561,69 @@ test("begins_typeahead", ({override, override_rewire}) => {
assert_typeahead_equals(
"test\n@i",
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,
),
);
assert_typeahead_equals(
"test\n@_i",
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,
),
);
assert_typeahead_equals(
"test\n @l",
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,
),
);
assert_typeahead_equals(
"test\n @_l",
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,
),
);
@ -1583,9 +1635,12 @@ test("begins_typeahead", ({override, override_rewire}) => {
assert_typeahead_equals(" @_zuli", []);
assert_typeahead_equals(
"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", []);
@ -1760,7 +1815,11 @@ test("begins_typeahead", ({override, override_rewire}) => {
assert_typeahead_equals("~~~test", "ing", []);
const terminal_symbols = ",.;?!()[]> \"'\n\t";
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_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");
let th_render_person_called = false;
override_rewire(typeahead_helper, "render_person", (person) => {
assert.deepEqual(person, othello);
assert.deepEqual(person, othello_item);
th_render_person_called = true;
});
ct.content_highlighter_html(othello);
ct.content_highlighter_html(othello_item);
let th_render_user_group_called = false;
override_rewire(typeahead_helper, "render_user_group", (user_group) => {
@ -1873,10 +1932,10 @@ test("filter_and_sort_mentions (normal)", () => {
current_user.user_id = 101;
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(
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
@ -1885,7 +1944,7 @@ test("filter_and_sort_mentions (normal)", () => {
suggestions = ct.filter_and_sort_mentions(is_silent, "al");
assert.deepEqual(
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
@ -1893,7 +1952,10 @@ test("filter_and_sort_mentions (normal)", () => {
// recursive subgroups.
current_user.user_id = 102;
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)", () => {
@ -1901,14 +1963,20 @@ test("filter_and_sort_mentions (silent)", () => {
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
// user is member of can_mention_group or its subgroups for a
// silent mention.
current_user.user_id = 102;
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", () => {
@ -2005,22 +2073,22 @@ test("typeahead_results", () => {
is_silent: false,
};
}
assert_mentions_matches("cordelia", [not_silent(cordelia)]);
assert_mentions_matches("cordelia, le", [not_silent(cordelia)]);
assert_mentions_matches("cordelia", [not_silent(cordelia_item)]);
assert_mentions_matches("cordelia, le", [not_silent(cordelia_item)]);
assert_mentions_matches("cordelia, le ", []);
assert_mentions_matches("moor", [not_silent(othello)]);
assert_mentions_matches("moor ", [not_silent(othello)]);
assert_mentions_matches("moor of", [not_silent(othello)]);
assert_mentions_matches("moor of ven", [not_silent(othello)]);
assert_mentions_matches("oor", [not_silent(othello)]);
assert_mentions_matches("moor", [not_silent(othello_item)]);
assert_mentions_matches("moor ", [not_silent(othello_item)]);
assert_mentions_matches("moor of", [not_silent(othello_item)]);
assert_mentions_matches("moor of ven", [not_silent(othello_item)]);
assert_mentions_matches("oor", [not_silent(othello_item)]);
assert_mentions_matches("oor ", []);
assert_mentions_matches("oor o", []);
assert_mentions_matches("oor of venice", []);
assert_mentions_matches("King ", [not_silent(hamlet), not_silent(lear)]);
assert_mentions_matches("King H", [not_silent(hamlet)]);
assert_mentions_matches("King L", [not_silent(lear)]);
assert_mentions_matches("King ", [not_silent(hamlet_item), not_silent(lear_item)]);
assert_mentions_matches("King H", [not_silent(hamlet_item)]);
assert_mentions_matches("King L", [not_silent(lear_item)]);
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
// 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,
// 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
// "everyone" and "stream" wildcard mentions.
assert_mentions_matches("e", [
not_silent(mention_everyone),
not_silent(hal),
not_silent(alice),
not_silent(cordelia),
not_silent(gael),
not_silent(hamlet),
not_silent(lear),
not_silent(othello),
not_silent(hal_item),
not_silent(alice_item),
not_silent(cordelia_item),
not_silent(gael_item),
not_silent(hamlet_item),
not_silent(lear_item),
not_silent(othello_item),
not_silent(hamletcharacters),
not_silent(call_center),
]);
// Verify we suggest both 'the first matching stream wildcard' and
// '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".
assert_mentions_matches("o", [
not_silent(othello),
not_silent(othello_item),
not_silent(mention_everyone),
not_silent(mention_topic),
not_silent(cordelia),
not_silent(cordelia_item),
]);
// Autocomplete by slash commands.
@ -2101,20 +2169,20 @@ test("message people", ({override, override_rewire}) => {
};
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.
user_ids = [hamlet.user_id, harry.user_id];
results = ct.get_person_suggestions("Ha", opts);
assert.deepEqual(results, [harry, hamlet]);
assert.deepEqual(results, [harry_item, hamlet_item]);
// Reincluding Hal and deactivating harry
user_ids = [hamlet.user_id, harry.user_id, hal.user_id];
people.deactivate(harry);
results = ct.get_person_suggestions("Ha", opts);
// 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", () => {
@ -2127,7 +2195,7 @@ test("muted users excluded from results", () => {
// Nobody is muted
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.
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
// or user group mentions.
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]);
});
@ -2163,12 +2231,12 @@ test("direct message recipients sorted according to stream / topic being viewed"
compose_state.set_stream_id("");
results = ct.get_pm_people("li");
// `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
compose_state.set_stream_id(denmark_stream.stream_id);
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.
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
// 1st despite having an exact name match with the query.
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) {
return {
...user,
type: "user",
};
return {type: "user", user};
}
const jill = {

View File

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

View File

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