"use strict"; const assert = require("node:assert/strict"); const {mock_esm, zrequire} = require("./lib/namespace.js"); const {run_test, noop} = require("./lib/test.js"); const {page_params} = require("./lib/zpage_params.js"); const narrow_state = mock_esm("../src/narrow_state"); const stream_topic_history_util = mock_esm("../src/stream_topic_history_util"); const direct_message_group_data = zrequire("direct_message_group_data"); const {Filter} = zrequire("filter"); const stream_data = zrequire("stream_data"); const stream_topic_history = zrequire("stream_topic_history"); const people = zrequire("people"); const search = zrequire("search_suggestion"); const {set_current_user, set_realm} = zrequire("state_data"); const current_user = {}; set_current_user(current_user); const realm = {}; set_realm(realm); const me = { email: "myself@zulip.com", full_name: "Me Myself", user_id: 41, }; const bob = { email: "bob@zulip.com", full_name: "Bob Roberts", user_id: 42, }; const ted = { email: "ted@zulip.com", delivery_email: "ted@zulip.com", user_id: 101, full_name: "Ted Smith", }; const alice = { email: "alice@zulip.com", user_id: 102, full_name: "Alice Ignore", }; const jeff = { email: "jeff@zulip.com", user_id: 103, full_name: "Jeff Zoolipson", }; const example_avatar_url = "http://example.com/example.png"; let _stream_id = 0; function new_stream_id() { _stream_id += 1; return _stream_id; } function init({override}) { override(current_user, "is_admin", true); people.init(); people.add_active_user(bob); people.add_active_user(me); people.add_active_user(ted); people.add_active_user(alice); people.add_active_user(jeff); people.initialize_current_user(me.user_id); stream_topic_history.reset(); direct_message_group_data.clear_for_testing(); stream_data.clear_subscriptions(); } function get_suggestions(query, pill_query = "") { return search.get_suggestions(Filter.parse(pill_query), Filter.parse(query)); } function test(label, f) { run_test(label, (helpers) => { init(helpers); f(helpers); }); } test("basic_get_suggestions", ({override}) => { const query = "fred"; override(narrow_state, "stream_id", noop); const suggestions = get_suggestions(query); const expected = ["fred"]; assert.deepEqual(suggestions.strings, expected); }); test("basic_get_suggestions_for_spectator", () => { page_params.is_spectator = true; const query = ""; const suggestions = get_suggestions(query); assert.deepEqual(suggestions.strings, [ "has:link", "has:image", "has:attachment", "has:reaction", ]); page_params.is_spectator = false; }); test("get_is_suggestions_for_spectator", () => { page_params.is_spectator = true; const query = "is:"; const suggestions = get_suggestions(query); // The list of suggestions should be empty for a spectator assert.deepEqual(suggestions.strings, []); page_params.is_spectator = false; }); test("subset_suggestions", ({mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); const denmark_id = new_stream_id(); const sub = {name: "Denmark", stream_id: denmark_id}; stream_data.add_sub(sub); const query = `channel:${denmark_id} topic:Hamlet shakespeare`; const suggestions = get_suggestions(query); const expected = [ `channel:${denmark_id} topic:Hamlet shakespeare`, `channel:${denmark_id} topic:Hamlet`, `channel:${denmark_id}`, ]; assert.deepEqual(suggestions.strings, expected); }); test("dm_suggestions", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); mock_template("user_pill.hbs", true, (_data, html) => html); let query = "is:dm"; let suggestions = get_suggestions(query); let expected = [ "is:dm", "dm:alice@zulip.com", "dm:bob@zulip.com", "dm:jeff@zulip.com", "dm:myself@zulip.com", "dm:ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); query = "is:dm al"; suggestions = get_suggestions(query); expected = [ "is:dm al", "is:dm is:alerted", "is:dm dm:alice@zulip.com", "is:dm sender:alice@zulip.com", "is:dm dm-including:alice@zulip.com", "is:dm", ]; assert.deepEqual(suggestions.strings, expected); // "is:private" was renamed to "is:dm", so // we suggest "is:dm" to anyone with "is:private" // in their muscle memory. query = "is:pr"; suggestions = get_suggestions(query); expected = ["is:dm"]; assert.deepEqual(suggestions.strings, expected); query = "is:private"; suggestions = get_suggestions(query); expected = ["is:dm"]; assert.deepEqual(suggestions.strings, expected); query = "dm:t"; suggestions = get_suggestions(query); expected = ["dm:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "-dm:t"; suggestions = get_suggestions(query); expected = ["is:dm -dm:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "dm:ted@zulip.com"; suggestions = get_suggestions(query); expected = ["dm:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:ted"; suggestions = get_suggestions(query); expected = ["sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:te"; suggestions = get_suggestions(query); expected = ["sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "-sender:te"; suggestions = get_suggestions(query); expected = ["-sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:ted@zulip.com"; suggestions = get_suggestions(query); expected = ["sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "is:unread from:ted"; suggestions = get_suggestions(query); expected = ["is:unread sender:ted@zulip.com", "is:unread"]; assert.deepEqual(suggestions.strings, expected); // Users can enter bizarre queries, and if they do, we want to // be conservative with suggestions. query = "is:dm near:3"; suggestions = get_suggestions(query); expected = ["is:dm near:3", "is:dm"]; assert.deepEqual(suggestions.strings, expected); query = "dm:ted@zulip.com near:3"; suggestions = get_suggestions(query); expected = ["dm:ted@zulip.com near:3", "dm:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); // Make sure suggestions still work if preceding tokens query = "is:alerted sender:ted@zulip.com"; suggestions = get_suggestions(query); expected = ["is:alerted sender:ted@zulip.com", "is:alerted"]; assert.deepEqual(suggestions.strings, expected); query = "is:starred has:link is:dm al"; suggestions = get_suggestions(query); expected = [ "is:starred has:link is:dm al", "is:starred has:link is:dm is:alerted", "is:starred has:link is:dm dm:alice@zulip.com", "is:starred has:link is:dm sender:alice@zulip.com", "is:starred has:link is:dm dm-including:alice@zulip.com", "is:starred has:link is:dm", "is:starred has:link", "is:starred", ]; assert.deepEqual(suggestions.strings, expected); query = "from:ted@zulip.com"; suggestions = get_suggestions(query); expected = ["sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); // "pm-with" operator returns search result // and "dm" operator as a suggestions override(narrow_state, "stream_id", () => undefined); query = "pm-with"; suggestions = get_suggestions(query); expected = ["pm-with", "dm:"]; assert.deepEqual(suggestions.strings, expected); query = "pm-with:t"; suggestions = get_suggestions(query); expected = ["dm:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); }); test("group_suggestions", ({mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); mock_template("user_pill.hbs", true, (_data, html) => html); // If there's an existing completed user pill right before // the input string, we suggest a user group as one of the // suggestions. let pill_query = "dm:bob@zulip.com"; let query = "alice"; let suggestions = get_suggestions(query, pill_query); let expected = [ "dm:bob@zulip.com alice", "dm:bob@zulip.com,alice@zulip.com", "dm:bob@zulip.com sender:alice@zulip.com", "dm:bob@zulip.com dm-including:alice@zulip.com", "dm:bob@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // Do not suggest "myself@zulip.com" (the name of the current user) for dms pill_query = "dm:ted@zulip.com"; query = "my"; suggestions = get_suggestions(query, pill_query); expected = [ "dm:ted@zulip.com my", "dm:ted@zulip.com sender:myself@zulip.com", "dm:ted@zulip.com dm-including:myself@zulip.com", "dm:ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // "is:dm" should be properly prepended to each suggestion // if the "dm" operator is negated. query = "-dm:bob@zulip.co"; suggestions = get_suggestions(query); expected = ["is:dm -dm:bob@zulip.com"]; assert.deepEqual(suggestions.strings, expected); // If user types "pm-with" operator, show suggestions for // group direct messages with the "dm" operator. pill_query = "pm-with:bob@zulip.com"; query = "alice"; suggestions = get_suggestions(query, pill_query); expected = [ "dm:bob@zulip.com alice", "dm:bob@zulip.com,alice@zulip.com", "dm:bob@zulip.com sender:alice@zulip.com", "dm:bob@zulip.com dm-including:alice@zulip.com", "dm:bob@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // Test multiple terms pill_query = "is:starred has:link dm:bob@zulip.com"; query = "Smit"; suggestions = get_suggestions(query, pill_query); expected = [ "is:starred has:link dm:bob@zulip.com Smit", "is:starred has:link dm:bob@zulip.com,ted@zulip.com", "is:starred has:link dm:bob@zulip.com sender:ted@zulip.com", "is:starred has:link dm:bob@zulip.com dm-including:ted@zulip.com", "is:starred has:link dm:bob@zulip.com", "is:starred has:link", "is:starred", ]; assert.deepEqual(suggestions.strings, expected); // Doesn't show dms because it's invalid in combination // with a channel. (Random channel id.) query = "channel:66 has:link dm:bob@zulip.com,Smit"; suggestions = get_suggestions(query); expected = ["channel:66 has:link", "channel:66"]; assert.deepEqual(suggestions.strings, expected); // Invalid emails don't give suggestions query = "has:link dm:invalid@zulip.com,Smit"; suggestions = get_suggestions(query); expected = ["has:link"]; assert.deepEqual(suggestions.strings, expected); }); test("empty_query_suggestions", () => { const query = ""; const devel_id = new_stream_id(); const office_id = new_stream_id(); stream_data.add_sub({stream_id: devel_id, name: "devel", subscribed: true}); stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true}); const suggestions = get_suggestions(query); const expected = [ "channels:public", "is:dm", "is:starred", "is:mentioned", "is:followed", "is:alerted", "is:unread", "is:resolved", "sender:myself@zulip.com", `channel:${devel_id}`, `channel:${office_id}`, "has:link", "has:image", "has:attachment", "has:reaction", ]; assert.deepEqual(suggestions.strings, expected); function describe(q) { return suggestions.lookup_table.get(q).description_html; } assert.equal(describe("is:dm"), "Direct messages"); assert.equal(describe("is:starred"), "Starred messages"); assert.equal(describe("is:mentioned"), "@-mentions"); assert.equal(describe("is:alerted"), "Alerted messages"); assert.equal(describe("is:unread"), "Unread messages"); assert.equal(describe("is:resolved"), "Topics marked as resolved"); assert.equal(describe("is:followed"), "Followed topics"); assert.equal(describe("sender:myself@zulip.com"), "Sent by me"); assert.equal(describe("has:link"), "Messages with links"); assert.equal(describe("has:image"), "Messages with images"); assert.equal(describe("has:attachment"), "Messages with attachments"); }); test("has_suggestions", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); // Checks that category wise suggestions are displayed instead of a single // default suggestion when suggesting `has` operator. let query = "h"; stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true}); stream_data.add_sub({stream_id: 77, name: "office", subscribed: true}); override(narrow_state, "stream_id", noop); let suggestions = get_suggestions(query); let expected = ["h", "has:link", "has:image", "has:attachment", "has:reaction"]; assert.deepEqual(suggestions.strings, expected); function describe(q) { return suggestions.lookup_table.get(q).description_html; } assert.equal(describe("has:link"), "Messages with links"); assert.equal(describe("has:image"), "Messages with images"); assert.equal(describe("has:attachment"), "Messages with attachments"); query = "-h"; suggestions = get_suggestions(query); expected = ["-h", "-has:link", "-has:image", "-has:attachment", "-has:reaction"]; assert.deepEqual(suggestions.strings, expected); assert.equal(describe("-has:link"), "Exclude messages with links"); assert.equal(describe("-has:image"), "Exclude messages with images"); assert.equal(describe("-has:attachment"), "Exclude messages with attachments"); // operand suggestions follow. query = "has:"; suggestions = get_suggestions(query); expected = ["has:link", "has:image", "has:attachment", "has:reaction"]; assert.deepEqual(suggestions.strings, expected); query = "has:im"; suggestions = get_suggestions(query); expected = ["has:image"]; assert.deepEqual(suggestions.strings, expected); query = "-has:im"; suggestions = get_suggestions(query); expected = ["-has:image"]; assert.deepEqual(suggestions.strings, expected); query = "att"; suggestions = get_suggestions(query); expected = ["att", "has:attachment"]; assert.deepEqual(suggestions.strings, expected); // 66 is misc channel id. query = "channel:66 is:alerted has:lin"; suggestions = get_suggestions(query); expected = ["channel:66 is:alerted has:link", "channel:66 is:alerted", "channel:66"]; assert.deepEqual(suggestions.strings, expected); }); test("check_is_suggestions", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); mock_template("user_pill.hbs", true, (_data, html) => html); override(narrow_state, "stream_id", noop); let query = "i"; let suggestions = get_suggestions(query); let expected = [ "i", "is:dm", "is:starred", "is:mentioned", "is:followed", "is:alerted", "is:unread", "is:resolved", "dm:alice@zulip.com", "sender:alice@zulip.com", "dm-including:alice@zulip.com", "has:image", ]; assert.deepEqual(suggestions.strings, expected); function describe(q) { return suggestions.lookup_table.get(q).description_html; } assert.equal(describe("is:dm"), "Direct messages"); assert.equal(describe("is:starred"), "Starred messages"); assert.equal(describe("is:mentioned"), "@-mentions"); assert.equal(describe("is:alerted"), "Alerted messages"); assert.equal(describe("is:unread"), "Unread messages"); assert.equal(describe("is:resolved"), "Topics marked as resolved"); assert.equal(describe("is:followed"), "Followed topics"); query = "-i"; suggestions = get_suggestions(query); expected = [ "-i", "-is:dm", "-is:starred", "-is:mentioned", "-is:followed", "-is:alerted", "-is:unread", "-is:resolved", ]; assert.deepEqual(suggestions.strings, expected); assert.equal(describe("-is:dm"), "Exclude direct messages"); assert.equal(describe("-is:starred"), "Exclude starred messages"); assert.equal(describe("-is:mentioned"), "Exclude @-mentions"); assert.equal(describe("-is:alerted"), "Exclude alerted messages"); assert.equal(describe("-is:unread"), "Exclude unread messages"); assert.equal(describe("-is:resolved"), "Exclude topics marked as resolved"); assert.equal(describe("-is:followed"), "Exclude followed topics"); // operand suggestions follow. query = "is:"; suggestions = get_suggestions(query); expected = [ "is:dm", "is:starred", "is:mentioned", "is:followed", "is:alerted", "is:unread", "is:resolved", ]; assert.deepEqual(suggestions.strings, expected); query = "is:st"; suggestions = get_suggestions(query); expected = ["is:starred"]; assert.deepEqual(suggestions.strings, expected); query = "-is:st"; suggestions = get_suggestions(query); expected = ["-is:starred"]; assert.deepEqual(suggestions.strings, expected); // Still returns suggestions for "streams:public", // but shows html description used for "channels:public" query = "st"; suggestions = get_suggestions(query); expected = ["st", "streams:public", "is:starred", "channel:"]; assert.deepEqual(suggestions.strings, expected); query = "channel:66 has:link is:sta"; suggestions = get_suggestions(query); expected = ["channel:66 has:link is:starred", "channel:66 has:link", "channel:66"]; assert.deepEqual(suggestions.strings, expected); }); test("sent_by_me_suggestions", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); override(narrow_state, "stream_id", noop); let query = ""; let suggestions = get_suggestions(query); assert.ok(suggestions.strings.includes("sender:myself@zulip.com")); assert.equal( suggestions.lookup_table.get("sender:myself@zulip.com").description_html, "Sent by me", ); query = "sender"; suggestions = get_suggestions(query); let expected = ["sender", "sender:myself@zulip.com", "sender:"]; assert.deepEqual(suggestions.strings, expected); query = "-sender"; suggestions = get_suggestions(query); expected = ["-sender", "-sender:myself@zulip.com", "-sender:"]; assert.deepEqual(suggestions.strings, expected); query = "from"; suggestions = get_suggestions(query); expected = ["from", "sender:myself@zulip.com", "sender:"]; assert.deepEqual(suggestions.strings, expected); query = "-from"; suggestions = get_suggestions(query); expected = ["-from", "-sender:myself@zulip.com", "-sender:"]; assert.deepEqual(suggestions.strings, expected); query = "sender:bob@zulip.com"; suggestions = get_suggestions(query); expected = ["sender:bob@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "from:bob@zulip.com"; suggestions = get_suggestions(query); expected = ["sender:bob@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sent"; suggestions = get_suggestions(query); expected = ["sent", "sender:myself@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "-sent"; suggestions = get_suggestions(query); expected = ["-sent", "-sender:myself@zulip.com"]; assert.deepEqual(suggestions.strings, expected); const denmark_id = new_stream_id(); const sub = {name: "Denmark", stream_id: denmark_id}; stream_data.add_sub(sub); query = `channel:${denmark_id} topic:Denmark1 sent`; suggestions = get_suggestions(query); expected = [ `channel:${denmark_id} topic:Denmark1 sent`, `channel:${denmark_id} topic:Denmark1 sender:myself@zulip.com`, `channel:${denmark_id} topic:Denmark1`, `channel:${denmark_id}`, ]; assert.deepEqual(suggestions.strings, expected); query = "is:starred sender:m"; suggestions = get_suggestions(query); expected = ["is:starred sender:myself@zulip.com", "is:starred"]; assert.deepEqual(suggestions.strings, expected); }); test("topic_suggestions", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); mock_template("user_pill.hbs", true, (_data, html) => html); let suggestions; let expected; override(stream_topic_history_util, "get_server_history", noop); const office_id = new_stream_id(); stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true}); override(narrow_state, "stream_id", () => office_id); const devel_id = new_stream_id(); stream_data.add_sub({stream_id: devel_id, name: "devel", subscribed: true}); stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true}); suggestions = get_suggestions("te"); expected = ["te", "dm:ted@zulip.com", "sender:ted@zulip.com", "dm-including:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); stream_topic_history.add_message({ stream_id: devel_id, topic_name: "REXX", }); for (const topic_name of ["team", "ignore", "test"]) { stream_topic_history.add_message({ stream_id: office_id, topic_name, }); } suggestions = get_suggestions("te"); expected = [ "te", "dm:ted@zulip.com", "sender:ted@zulip.com", "dm-including:ted@zulip.com", `channel:${office_id} topic:team`, `channel:${office_id} topic:test`, ]; assert.deepEqual(suggestions.strings, expected); function describe(q) { return suggestions.lookup_table.get(q).description_html; } assert.equal(describe("te"), "Search for te"); assert.equal(describe(`channel:${office_id} topic:team`), "Channel office > team"); suggestions = get_suggestions(`topic:staplers channel:${office_id}`); expected = [`topic:staplers channel:${office_id}`, "topic:staplers"]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions(`channel:${devel_id} topic:`); expected = [ `channel:${devel_id} topic:`, `channel:${devel_id} topic:REXX`, `channel:${devel_id}`, ]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions(`channel:${devel_id} -topic:`); expected = [ `channel:${devel_id} -topic:`, `channel:${devel_id} -topic:REXX`, `channel:${devel_id}`, ]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions("-topic:te"); expected = [ "-topic:te", `channel:${office_id} -topic:team`, `channel:${office_id} -topic:test`, ]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions(`is:alerted channel:${devel_id} is:starred topic:`); expected = [ `is:alerted channel:${devel_id} is:starred topic:`, `is:alerted channel:${devel_id} is:starred topic:REXX`, `is:alerted channel:${devel_id} is:starred`, `is:alerted channel:${devel_id}`, "is:alerted", ]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions(`is:dm channel:${devel_id} topic:`); expected = [`is:dm channel:${devel_id} topic:`, `is:dm channel:${devel_id}`, `is:dm`]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions(`topic:REXX channel:${devel_id} topic:`); expected = [ `topic:REXX channel:${devel_id} topic:`, `topic:REXX channel:${devel_id}`, "topic:REXX", ]; assert.deepEqual(suggestions.strings, expected); }); test("topic_suggestions (limits)", () => { let candidate_topics = []; function assert_result(guess, expected_topics) { assert.deepEqual( search.get_topic_suggestions_from_candidates({candidate_topics, guess}), expected_topics, ); } assert_result("", []); assert_result("zzz", []); candidate_topics = ["a", "b", "c"]; assert_result("", ["a", "b", "c"]); assert_result("b", ["b"]); assert_result("z", []); candidate_topics = [ "a1", "a2", "b1", "b2", "a3", "a4", "a5", "c1", "a6", "a7", "a8", "c2", "a9", "a10", "a11", "a12", ]; // We max out at 10 topics, so as not to overwhelm the user. assert_result("", ["a1", "a2", "b1", "b2", "a3", "a4", "a5", "c1", "a6", "a7"]); assert_result("a", ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10"]); assert_result("b", ["b1", "b2"]); assert_result("z", []); }); test("whitespace_glitch", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); const office_stream_id = new_stream_id(); const query = "channel:office "; // note trailing space override(stream_topic_history_util, "get_server_history", noop); stream_data.add_sub({stream_id: office_stream_id, name: "office", subscribed: true}); const suggestions = get_suggestions(query); const expected = [`channel:${office_stream_id}`]; assert.deepEqual(suggestions.strings, expected); }); test("channel_completion", ({override}) => { const office_stream_id = new_stream_id(); stream_data.add_sub({stream_id: office_stream_id, name: "office", subscribed: true}); const dev_help_stream_id = new_stream_id(); stream_data.add_sub({stream_id: dev_help_stream_id, name: "dev help", subscribed: true}); override(narrow_state, "stream_id", noop); let query = "channel:of"; let suggestions = get_suggestions(query); let expected = [`channel:${office_stream_id}`]; assert.deepEqual(suggestions.strings, expected); query = "-channel:of"; suggestions = get_suggestions(query); expected = [`-channel:${office_stream_id}`]; assert.deepEqual(suggestions.strings, expected); query = "hel"; suggestions = get_suggestions(query); expected = ["hel", `channel:${dev_help_stream_id}`]; assert.deepEqual(suggestions.strings, expected); }); test("people_suggestions", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); mock_template("user_pill.hbs", true, (_data, html) => html); let query = "te"; override(narrow_state, "stream_id", noop); const ted = { email: "ted@zulip.com", user_id: 201, full_name: "Ted Smith", }; const bob = { email: "bob@zulip.com", user_id: 202, full_name: "Bob Térry", avatar_url: example_avatar_url, is_guest: false, }; const alice = { email: "alice@zulip.com", user_id: 203, full_name: "Alice Ignore", }; people.add_active_user(ted); people.add_active_user(bob); people.add_active_user(alice); // Add an inaccessible user to verify that it is not included in // suggestions. const inaccessible_user = { user_id: 299, // All inaccessible users are named as "Unknown user", but we name // it differently here so that the name matches the search query. full_name: "Test unknown user", email: "user299@zulipdev.com", is_inaccessible_user: true, }; people._add_user(inaccessible_user); let suggestions = get_suggestions(query); let expected = [ "te", "dm:bob@zulip.com", // bob térry "dm:ted@zulip.com", "sender:bob@zulip.com", "sender:ted@zulip.com", "dm-including:bob@zulip.com", "dm-including:ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); const accessible_user = { user_id: 299, full_name: "Test unknown user", email: "user299@zulipdev.com", }; people.add_active_user(accessible_user); suggestions = get_suggestions(query); expected = [ "te", "dm:bob@zulip.com", "dm:ted@zulip.com", "dm:user299@zulipdev.com", "sender:bob@zulip.com", "sender:ted@zulip.com", "sender:user299@zulipdev.com", "dm-including:bob@zulip.com", "dm-including:ted@zulip.com", "dm-including:user299@zulipdev.com", ]; assert.deepEqual(suggestions.strings, expected); function is_person(q) { return suggestions.lookup_table.get(q).description_html.includes(`class="user-pill`); } assert.equal(is_person("dm:ted@zulip.com"), true); assert.equal(is_person("sender:ted@zulip.com"), true); assert.equal(is_person("dm-including:ted@zulip.com"), true); function has_image(q) { return suggestions.lookup_table.get(q).description_html.includes(`class="pill-image"`); } assert.equal(has_image("dm:bob@zulip.com"), true); assert.equal(has_image("sender:bob@zulip.com"), true); assert.equal(has_image("dm-including:bob@zulip.com"), true); function test_describe(q, description_html_start) { assert.ok( suggestions.lookup_table.get(q).description_html.startsWith(description_html_start), ); } test_describe("dm:ted@zulip.com", "Direct messages with"); test_describe("sender:ted@zulip.com", "Sent by"); test_describe("dm-including:ted@zulip.com", "Direct messages including"); let expectedString = "Ted Smith"; function test_full_name(q, full_name_html) { return suggestions.lookup_table.get(q).description_html.includes(full_name_html); } test_full_name("sender:ted@zulip.com", expectedString); test_full_name("dm:ted@zulip.com", expectedString); test_full_name("dm-including:ted@zulip.com", expectedString); expectedString = example_avatar_url; function test_avatar_url(q, avatar_url) { return suggestions.lookup_table.get(q).description_html.includes(avatar_url); } test_avatar_url("dm:bob@zulip.com", expectedString); test_avatar_url("sender:bob@zulip.com", expectedString); test_avatar_url("dm-including:bob@zulip.com", expectedString); const guest_html = "(guest)"; function test_guest_user_indicator(q, should_have_guest_indicator) { const description_html = suggestions.lookup_table.get(q).description_html; if (should_have_guest_indicator) { assert.ok(description_html.includes(guest_html)); } else { assert.ok(!description_html.includes(guest_html)); } } override(realm, "realm_enable_guest_user_indicator", true); suggestions = get_suggestions(query); test_guest_user_indicator("dm:bob@zulip.com", false); test_guest_user_indicator("sender:bob@zulip.com", false); test_guest_user_indicator("dm-including:bob@zulip.com", false); bob.is_guest = true; suggestions = get_suggestions(query); test_guest_user_indicator("dm:bob@zulip.com", true); test_guest_user_indicator("sender:bob@zulip.com", true); test_guest_user_indicator("dm-including:bob@zulip.com", true); override(realm, "realm_enable_guest_user_indicator", false); suggestions = get_suggestions(query); test_guest_user_indicator("dm:bob@zulip.com", false); test_guest_user_indicator("sender:bob@zulip.com", false); test_guest_user_indicator("dm-including:bob@zulip.com", false); suggestions = get_suggestions("Ted "); // note space expected = ["Ted", "dm:ted@zulip.com", "sender:ted@zulip.com", "dm-including:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:ted sm"; expected = ["sender:ted@zulip.com"]; suggestions = get_suggestions(query); assert.deepEqual(suggestions.strings, expected); query = "sender:ted@zulip.com new"; expected = ["sender:ted@zulip.com new", "sender:ted@zulip.com"]; suggestions = get_suggestions(query); assert.deepEqual(suggestions.strings, expected); query = "sender:ted@tulip.com new"; expected = []; // Invalid email suggestions = get_suggestions(query); assert.deepEqual(suggestions.strings, expected); }); test("operator_suggestions", ({override, mock_template}) => { mock_template("search_description.hbs", true, (_data, html) => html); override(narrow_state, "stream_id", () => undefined); // Completed operator should return nothing let query = "channel:"; let suggestions = get_suggestions(query); let expected = []; assert.deepEqual(suggestions.strings, expected); query = "ch"; suggestions = get_suggestions(query); expected = ["ch", "channels:public", "channel:"]; assert.deepEqual(suggestions.strings, expected); query = "-s"; suggestions = get_suggestions(query); expected = ["-s", "-sender:myself@zulip.com", "-sender:", "-channel:"]; assert.deepEqual(suggestions.strings, expected); // 66 is a misc channel id. query = "channel:66 is:alerted -f"; suggestions = get_suggestions(query); expected = [ "channel:66 is:alerted -f", "channel:66 is:alerted -sender:myself@zulip.com", "channel:66 is:alerted -sender:", "channel:66 is:alerted", "channel:66", ]; assert.deepEqual(suggestions.strings, expected); }); test("queries_with_spaces", () => { const office_id = new_stream_id(); stream_data.add_sub({stream_id: office_id, name: "office", subscribed: true}); const dev_help_id = new_stream_id(); stream_data.add_sub({stream_id: dev_help_id, name: "dev help", subscribed: true}); // test allowing spaces with quotes surrounding operand let query = 'channel:"dev he"'; let suggestions = get_suggestions(query); let expected = [`channel:${dev_help_id}`]; assert.deepEqual(suggestions.strings, expected); // test mismatched quote query = 'channel:"dev h'; suggestions = get_suggestions(query); expected = [`channel:${dev_help_id}`]; assert.deepEqual(suggestions.strings, expected); // test extra space after operator still works query = "channel: offi"; suggestions = get_suggestions(query); expected = [`channel:${office_id}`]; assert.deepEqual(suggestions.strings, expected); });