"use strict"; const {strict: assert} = require("assert"); const {mock_esm, zrequire} = require("./lib/namespace"); const {run_test} = require("./lib/test"); const {page_params} = require("./lib/zpage_params"); const narrow_state = mock_esm("../src/narrow_state"); const stream_topic_history_util = mock_esm("../src/stream_topic_history_util"); const huddle_data = zrequire("huddle_data"); const stream_data = zrequire("stream_data"); const stream_topic_history = zrequire("stream_topic_history"); const people = zrequire("people"); const search = zrequire("search_suggestion"); search.__Rewire__("max_num_of_search_results", 15); 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 noop = () => {}; const example_avatar_url = "http://example.com/example.png"; function init() { page_params.is_admin = true; page_params.search_pills_enabled = 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(); huddle_data.clear_for_testing(); stream_data.clear_subscriptions(); } function get_suggestions(base_query, query) { return search.get_suggestions(base_query, query); } function test(label, f) { run_test(label, (helpers) => { init(); f(helpers); }); } test("basic_get_suggestions", ({override}) => { const query = "fred"; override(narrow_state, "stream", () => "office"); 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"]); page_params.is_spectator = false; }); test("subset_suggestions", () => { const query = "shakespeare"; const base_query = "stream:Denmark topic:Hamlet"; const suggestions = get_suggestions(base_query, query); const expected = ["shakespeare"]; assert.deepEqual(suggestions.strings, expected); }); test("dm_suggestions", ({override}) => { 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 = "al"; let base_query = "is:dm"; suggestions = get_suggestions(base_query, query); expected = [ "al", "is:alerted", "sender:alice@zulip.com", "dm:alice@zulip.com", "dm-including:alice@zulip.com", ]; 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:t", "dm:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "-dm:t"; suggestions = get_suggestions("", query); expected = ["-dm:t", "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", "sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:te"; suggestions = get_suggestions("", query); expected = ["sender:te", "sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "-sender:te"; suggestions = get_suggestions("", query); expected = ["-sender:te", "-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 = "from:ted"; base_query = "is:unread"; suggestions = get_suggestions(base_query, query); expected = ["from:ted", "from:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); // Users can enter bizarre queries, and if they do, we want to // be conservative with suggestions. query = "near:3"; base_query = "is:dm"; suggestions = get_suggestions(base_query, query); expected = ["near:3"]; assert.deepEqual(suggestions.strings, expected); query = "near:3"; base_query = "dm:ted@zulip.com"; suggestions = get_suggestions(base_query, query); expected = ["near:3"]; assert.deepEqual(suggestions.strings, expected); // Make sure suggestions still work if preceding tokens query = "sender:ted@zulip.com"; base_query = "is:alerted"; suggestions = get_suggestions(base_query, query); expected = ["sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "al"; base_query = "is:starred has:link is:dm"; suggestions = get_suggestions(base_query, query); expected = [ "al", "is:alerted", "sender:alice@zulip.com", "dm:alice@zulip.com", "dm-including:alice@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // Make sure it handles past context correctly query = "dm:"; base_query = "stream:Denmark"; suggestions = get_suggestions(base_query, query); expected = ["dm:"]; assert.deepEqual(suggestions.strings, expected); query = "sender:"; base_query = "sender:ted@zulip.com"; suggestions = get_suggestions(base_query, query); expected = ["sender:"]; assert.deepEqual(suggestions.strings, expected); // "pm-with" operator returns search result // and "dm" operator as a suggestions override(narrow_state, "stream", () => 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 = ["pm-with:t", "dm:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); }); test("group_suggestions", () => { // Entering a comma in a dm query should immediately generate // suggestions for the next person. let query = "dm:bob@zulip.com,"; let suggestions = get_suggestions("", query); let expected = [ "dm:bob@zulip.com,", "dm:bob@zulip.com,alice@zulip.com", "dm:bob@zulip.com,jeff@zulip.com", "dm:bob@zulip.com,ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // Only the last part of a comma-separated dm query should be used to // generate suggestions. query = "dm:bob@zulip.com,t"; suggestions = get_suggestions("", query); expected = ["dm:bob@zulip.com,t", "dm:bob@zulip.com,ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); // Smit should also generate ted@zulip.com (Ted Smith) as a suggestion. query = "dm:bob@zulip.com,Smit"; suggestions = get_suggestions("", query); expected = ["dm:bob@zulip.com,Smit", "dm:bob@zulip.com,ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); // Do not suggest "myself@zulip.com" (the name of the current user) query = "dm:ted@zulip.com,mys"; suggestions = get_suggestions("", query); expected = ["dm:ted@zulip.com,mys"]; assert.deepEqual(suggestions.strings, expected); // No superfluous suggestions should be generated. query = "dm:bob@zulip.com,red"; suggestions = get_suggestions("", query); expected = ["dm:bob@zulip.com,red"]; assert.deepEqual(suggestions.strings, expected); // "is:dm" should be properly prepended to each suggestion // if the "dm" operator is negated. query = "-dm:bob@zulip.com,"; suggestions = get_suggestions("", query); expected = [ "-dm:bob@zulip.com,", "is:dm -dm:bob@zulip.com,alice@zulip.com", "is:dm -dm:bob@zulip.com,jeff@zulip.com", "is:dm -dm:bob@zulip.com,ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); query = "-dm:bob@zulip.com,t"; suggestions = get_suggestions("", query); expected = ["-dm:bob@zulip.com,t", "is:dm -dm:bob@zulip.com,ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "-dm:bob@zulip.com,Smit"; suggestions = get_suggestions("", query); expected = ["-dm:bob@zulip.com,Smit", "is:dm -dm:bob@zulip.com,ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "-dm:bob@zulip.com,red"; suggestions = get_suggestions("", query); expected = ["-dm:bob@zulip.com,red"]; assert.deepEqual(suggestions.strings, expected); // If user types "pm-with" operator, an email and a comma, // show suggestions for group direct messages with the "dm" // operator. query = "pm-with:bob@zulip.com,"; suggestions = get_suggestions("", query); expected = [ "pm-with:bob@zulip.com,", "dm:bob@zulip.com,alice@zulip.com", "dm:bob@zulip.com,jeff@zulip.com", "dm:bob@zulip.com,ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // Test multiple operators query = "dm:bob@zulip.com,Smit"; let base_query = "is:starred has:link"; suggestions = get_suggestions(base_query, query); expected = ["dm:bob@zulip.com,Smit", "dm:bob@zulip.com,ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "dm:bob@zulip.com,Smit"; base_query = "stream:Denmark has:link"; suggestions = get_suggestions(base_query, query); expected = ["dm:bob@zulip.com,Smit"]; assert.deepEqual(suggestions.strings, expected); function message(user_ids, timestamp) { return { type: "private", display_recipient: user_ids.map((id) => ({ id, })), timestamp, }; } huddle_data.process_loaded_messages([ message([bob.user_id, ted.user_id], 99), message([bob.user_id, ted.user_id, jeff.user_id], 98), ]); // Simulate a past group direct message which should now // prioritize ted over alice query = "dm:bob@zulip.com,"; suggestions = get_suggestions("", query); expected = [ "dm:bob@zulip.com,", "dm:bob@zulip.com,ted@zulip.com", "dm:bob@zulip.com,alice@zulip.com", "dm:bob@zulip.com,jeff@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // bob, ted, and jeff are already an existing group direct message, // so prioritize this one query = "dm:bob@zulip.com,ted@zulip.com,"; suggestions = get_suggestions("", query); expected = [ "dm:bob@zulip.com,ted@zulip.com,", "dm:bob@zulip.com,ted@zulip.com,jeff@zulip.com", "dm:bob@zulip.com,ted@zulip.com,alice@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); // bob, ted, and jeff are already an existing group direct message, // but if we start with just jeff, then don't prioritize ted over // alice because it doesn't complete the full group direct message. query = "dm:jeff@zulip.com,"; suggestions = get_suggestions("", query); expected = [ "dm:jeff@zulip.com,", "dm:jeff@zulip.com,alice@zulip.com", "dm:jeff@zulip.com,bob@zulip.com", "dm:jeff@zulip.com,ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); query = "dm:jeff@zulip.com,ted@zulip.com hi"; suggestions = get_suggestions("", query); expected = ["dm:jeff@zulip.com,ted@zulip.com hi"]; assert.deepEqual(suggestions.strings, expected); }); test("empty_query_suggestions", () => { const query = ""; stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true}); stream_data.add_sub({stream_id: 77, name: "office", subscribed: true}); const suggestions = get_suggestions("", query); const expected = [ "", "streams:public", "is:dm", "is:starred", "is:mentioned", "is:alerted", "is:unread", "is:resolved", "sender:myself@zulip.com", "stream:devel", "stream:office", "has:link", "has:image", "has:attachment", ]; 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("sender:myself@zulip.com"), "Sent by me"); assert.equal(describe("has:link"), "Messages with one or more link"); assert.equal(describe("has:image"), "Messages with one or more image"); assert.equal(describe("has:attachment"), "Messages with one or more attachment"); }); test("has_suggestions", ({override}) => { // 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", () => {}); let suggestions = get_suggestions("", query); let expected = ["h", "has:link", "has:image", "has:attachment"]; assert.deepEqual(suggestions.strings, expected); function describe(q) { return suggestions.lookup_table.get(q).description_html; } assert.equal(describe("has:link"), "Messages with one or more link"); assert.equal(describe("has:image"), "Messages with one or more image"); assert.equal(describe("has:attachment"), "Messages with one or more attachment"); query = "-h"; suggestions = get_suggestions("", query); expected = ["-h", "-has:link", "-has:image", "-has:attachment"]; assert.deepEqual(suggestions.strings, expected); assert.equal(describe("-has:link"), "Exclude messages with one or more link"); assert.equal(describe("-has:image"), "Exclude messages with one or more image"); assert.equal(describe("-has:attachment"), "Exclude messages with one or more attachment"); // operand suggestions follow. query = "has:"; suggestions = get_suggestions("", query); expected = ["has:link", "has:image", "has:attachment"]; 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); query = "has:lin"; const base_query = "stream:Denmark is:alerted"; suggestions = get_suggestions(base_query, query); expected = ["has:link"]; assert.deepEqual(suggestions.strings, expected); }); test("check_is_suggestions", ({override}) => { let query = "i"; 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", () => {}); let suggestions = get_suggestions("", query); let expected = [ "i", "is:dm", "is:starred", "is:mentioned", "is:alerted", "is:unread", "is:resolved", "sender:alice@zulip.com", "dm: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"); query = "-i"; suggestions = get_suggestions("", query); expected = [ "-i", "-is:dm", "-is:starred", "-is:mentioned", "-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"); query = ""; suggestions = get_suggestions("", query); expected = [ "", "streams:public", "is:dm", "is:starred", "is:mentioned", "is:alerted", "is:unread", "is:resolved", "sender:myself@zulip.com", "stream:devel", "stream:office", "has:link", "has:image", "has:attachment", ]; assert.deepEqual(suggestions.strings, expected); query = ""; let base_query = "is:dm"; suggestions = get_suggestions(base_query, query); expected = [ "is:starred", "is:mentioned", "is:alerted", "is:unread", "sender:myself@zulip.com", "has:link", "has:image", "has:attachment", ]; assert.deepEqual(suggestions.strings, expected); // operand suggestions follow. query = "is:"; suggestions = get_suggestions("", query); expected = ["is:dm", "is:starred", "is:mentioned", "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); query = "st"; suggestions = get_suggestions("", query); expected = ["st", "streams:public", "is:starred", "stream:"]; assert.deepEqual(suggestions.strings, expected); query = "is:sta"; base_query = "stream:Denmark has:link"; suggestions = get_suggestions(base_query, query); expected = ["is:starred"]; assert.deepEqual(suggestions.strings, expected); }); test("sent_by_me_suggestions", ({override}) => { override(narrow_state, "stream", () => {}); 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", "from:myself@zulip.com", "from:"]; assert.deepEqual(suggestions.strings, expected); query = "-from"; suggestions = get_suggestions("", query); expected = ["-from", "-from:myself@zulip.com", "-from:"]; 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 = ["from: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); query = "sent"; let base_query = "stream:Denmark topic:Denmark1"; suggestions = get_suggestions(base_query, query); expected = ["sent", "sender:myself@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:m"; base_query = "is:starred"; suggestions = get_suggestions(base_query, query); expected = ["sender:m", "sender:myself@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:"; base_query = "is:starred"; suggestions = get_suggestions(base_query, query); expected = [ "sender:", "sender:myself@zulip.com", "sender:alice@zulip.com", "sender:bob@zulip.com", "sender:jeff@zulip.com", "sender:ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); }); test("topic_suggestions", ({override}) => { let suggestions; let expected; override(stream_topic_history_util, "get_server_history", () => {}); stream_data.add_sub({stream_id: 77, name: "office", subscribed: true}); override(narrow_state, "stream", () => "office"); const devel_id = 44; const office_id = 77; 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", "sender:ted@zulip.com", "dm: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", "sender:ted@zulip.com", "dm:ted@zulip.com", "dm-including:ted@zulip.com", "stream:office topic:team", "stream:office 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("stream:office topic:team"), "Stream office > team"); suggestions = get_suggestions("topic:staplers", "stream:office"); expected = ["stream:office"]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions("stream:devel", "topic:"); expected = ["topic:", "topic:REXX"]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions("stream:devel", "-topic:"); expected = ["-topic:", "-topic:REXX"]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions("", "-topic:te"); expected = ["-topic:te", "stream:office -topic:team", "stream:office -topic:test"]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions("is:alerted stream:devel is:starred", "topic:"); expected = ["topic:", "topic:REXX"]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions("is:dm stream:devel", "topic:"); expected = ["topic:"]; assert.deepEqual(suggestions.strings, expected); suggestions = get_suggestions("topic:REXX stream:devel", "topic:"); expected = ["topic:"]; 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}) => { const query = "stream:office "; // note trailing space override(stream_topic_history_util, "get_server_history", () => {}); stream_data.add_sub({stream_id: 77, name: "office", subscribed: true}); const suggestions = get_suggestions("", query); const expected = ["stream:office"]; assert.deepEqual(suggestions.strings, expected); }); test("stream_completion", ({override}) => { stream_data.add_sub({stream_id: 77, name: "office", subscribed: true}); stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true}); override(narrow_state, "stream", () => {}); let query = "stream:of"; let suggestions = get_suggestions("", query); let expected = ["stream:of", "stream:office"]; assert.deepEqual(suggestions.strings, expected); query = "-stream:of"; suggestions = get_suggestions("", query); expected = ["-stream:of", "-stream:office"]; assert.deepEqual(suggestions.strings, expected); query = "hel"; suggestions = get_suggestions("", query); expected = ["hel", "stream:dev+help"]; assert.deepEqual(suggestions.strings, expected); }); function people_suggestion_setup() { const ted = { email: "ted@zulip.com", user_id: 201, full_name: "Ted Smith", }; people.add_active_user(ted); const bob = { email: "bob@zulip.com", user_id: 202, full_name: "Bob Térry", avatar_url: example_avatar_url, }; people.add_active_user(bob); const alice = { email: "alice@zulip.com", user_id: 203, full_name: "Alice Ignore", }; people.add_active_user(alice); } test("people_suggestions", ({override}) => { override(narrow_state, "stream", noop); people_suggestion_setup(); let query = "te"; let suggestions = get_suggestions("", query); let expected = [ "te", "sender:bob@zulip.com", "sender:ted@zulip.com", "dm:bob@zulip.com", // bob térry "dm:ted@zulip.com", "dm-including:bob@zulip.com", "dm-including:ted@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); const is_person = (q) => suggestions.lookup_table.get(q).is_person; 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); const has_image = (q) => suggestions.lookup_table.get(q).user_pill_context.has_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); const describe = (q) => suggestions.lookup_table.get(q).description_html; assert.equal(describe("dm:ted@zulip.com"), "Direct messages with"); assert.equal(describe("sender:ted@zulip.com"), "Sent by"); assert.equal(describe("dm-including:ted@zulip.com"), "Direct messages including"); let expectedString = "Ted Smith"; const get_full_name = (q) => suggestions.lookup_table.get(q).user_pill_context.display_value.string; assert.equal(get_full_name("sender:ted@zulip.com"), expectedString); assert.equal(get_full_name("dm:ted@zulip.com"), expectedString); assert.equal(get_full_name("dm-including:ted@zulip.com"), expectedString); expectedString = `${example_avatar_url}?s=50`; const get_avatar_url = (q) => suggestions.lookup_table.get(q).user_pill_context.img_src; assert.equal(get_avatar_url("dm:bob@zulip.com"), expectedString); assert.equal(get_avatar_url("sender:bob@zulip.com"), expectedString); assert.equal(get_avatar_url("dm-including:bob@zulip.com"), expectedString); suggestions = get_suggestions("", "Ted "); // note space expected = ["Ted", "sender:ted@zulip.com", "dm:ted@zulip.com", "dm-including:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); query = "sender:ted sm"; let base_query = ""; expected = ["sender:ted+sm", "sender:ted@zulip.com"]; suggestions = get_suggestions(base_query, query); assert.deepEqual(suggestions.strings, expected); query = "new"; base_query = "sender:ted@zulip.com"; expected = ["new"]; suggestions = get_suggestions(base_query, query); assert.deepEqual(suggestions.strings, expected); query = "sender:ted@tulip.com new"; base_query = ""; expected = ["sender:ted@tulip.com+new"]; suggestions = get_suggestions(base_query, query); assert.deepEqual(suggestions.strings, expected); query = "new"; base_query = "sender:ted@tulip.com"; expected = ["new"]; suggestions = get_suggestions(base_query, query); assert.deepEqual(suggestions.strings, expected); }); test("operator_suggestions", ({override}) => { override(narrow_state, "stream", () => undefined); // Completed operator should return nothing let query = "stream:"; let suggestions = get_suggestions("", query); let expected = ["stream:"]; assert.deepEqual(suggestions.strings, expected); query = "st"; suggestions = get_suggestions("", query); expected = ["st", "streams:public", "is:starred", "stream:"]; assert.deepEqual(suggestions.strings, expected); query = "-s"; suggestions = get_suggestions("", query); expected = ["-s", "-streams:public", "-sender:myself@zulip.com", "-stream:", "-sender:"]; assert.deepEqual(suggestions.strings, expected); query = "-f"; const base_query = "stream:Denmark is:alerted"; suggestions = get_suggestions(base_query, query); expected = ["-f", "-from:myself@zulip.com", "-from:"]; assert.deepEqual(suggestions.strings, expected); }); test("queries_with_spaces", () => { stream_data.add_sub({stream_id: 77, name: "office", subscribed: true}); stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true}); // test allowing spaces with quotes surrounding operand let query = 'stream:"dev he"'; let suggestions = get_suggestions("", query); let expected = ["stream:dev+he", "stream:dev+help"]; assert.deepEqual(suggestions.strings, expected); // test mismatched quote query = 'stream:"dev h'; suggestions = get_suggestions("", query); expected = ["stream:dev+h", "stream:dev+help"]; assert.deepEqual(suggestions.strings, expected); // test extra space after operator still works query = "stream: offi"; suggestions = get_suggestions("", query); expected = ["stream:offi", "stream:office"]; assert.deepEqual(suggestions.strings, expected); }); // When input search query contains multiple operators // and a pill hasn't been formed from those operators. test("multiple_operators_without_pills", () => { let query = "is:dm al"; let base_query = ""; let suggestions = get_suggestions(base_query, query); let expected = [ "is:dm al", "is:dm is:alerted", "is:dm sender:alice@zulip.com", "is:dm dm:alice@zulip.com", "is:dm dm-including:alice@zulip.com", ]; assert.deepEqual(suggestions.strings, expected); query = "abc is:alerted sender:ted@zulip.com"; base_query = ""; suggestions = get_suggestions(base_query, query); expected = ["abc is:alerted sender:ted@zulip.com"]; assert.deepEqual(suggestions.strings, expected); });