// Unit test the search_suggestion.js module.
//
// These tests are framework-free and run sequentially; they are invoked
// immediately after being defined. The contract here is that tests should
// clean up after themselves, and they should explicitly stub all
// dependencies.
add_dependencies({
util: 'js/util.js',
Handlebars: 'handlebars',
Filter: 'js/filter.js',
typeahead_helper: 'js/typeahead_helper.js',
people: 'js/people.js',
stream_data: 'js/stream_data.js',
topic_data: 'js/topic_data.js',
narrow_state: 'js/narrow_state.js',
});
var people = global.people;
var search = require('js/search_suggestion.js');
var bob = {
email: 'bob@zulip.com',
full_name: 'Bob Roberts',
user_id: 42,
};
function init() {
people.init();
people.add(bob);
people.initialize_current_user(bob.user_id);
}
init();
set_global('narrow', {});
topic_data.reset();
(function test_basic_get_suggestions() {
var query = 'fred';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return 'office';
};
var suggestions = search.get_suggestions(query);
var expected = [
'fred',
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_subset_suggestions() {
var query = 'stream:Denmark topic:Hamlet shakespeare';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return undefined;
};
var suggestions = search.get_suggestions(query);
var expected = [
"stream:Denmark topic:Hamlet shakespeare",
"stream:Denmark topic:Hamlet",
"stream:Denmark",
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_private_suggestions() {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return undefined;
};
var ted =
{
email: 'ted@zulip.com',
user_id: 101,
full_name: 'Ted Smith',
};
var alice =
{
email: 'alice@zulip.com',
user_id: 102,
full_name: 'Alice Ignore',
};
people.add(ted);
people.add(alice);
var query = 'is:private';
var suggestions = search.get_suggestions(query);
var expected = [
"is:private",
"pm-with:alice@zulip.com",
"pm-with:bob@zulip.com",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:private al';
suggestions = search.get_suggestions(query);
expected = [
"is:private al",
"is:private is:alerted",
"is:private pm-with:alice@zulip.com",
"is:private sender:alice@zulip.com",
"is:private",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:t';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:t",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:t';
suggestions = search.get_suggestions(query);
expected = [
"-pm-with:t",
"is:private -pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:ted@zulip.com';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted';
suggestions = search.get_suggestions(query);
expected = [
"sender:ted",
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:te';
suggestions = search.get_suggestions(query);
expected = [
"sender:te",
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-sender:te';
suggestions = search.get_suggestions(query);
expected = [
"-sender:te",
"-sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted@zulip.com';
suggestions = search.get_suggestions(query);
expected = [
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:unread from:ted';
suggestions = search.get_suggestions(query);
expected = [
"is:unread from:ted",
"is:unread from: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:private near:3';
suggestions = search.get_suggestions(query);
expected = [
"is:private near:3",
"is:private",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:ted@zulip.com near:3';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:ted@zulip.com near:3",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Make sure suggestions still work if preceding tokens
query = 'is:alerted sender:ted@zulip.com';
suggestions = search.get_suggestions(query);
expected = [
"is:alerted sender:ted@zulip.com",
"is:alerted",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:starred has:link is:private al';
suggestions = search.get_suggestions(query);
expected = [
"is:starred has:link is:private al",
"is:starred has:link is:private is:alerted",
"is:starred has:link is:private pm-with:alice@zulip.com",
"is:starred has:link is:private sender:alice@zulip.com",
"is:starred has:link is:private",
"is:starred has:link",
"is:starred",
];
assert.deepEqual(suggestions.strings, expected);
// Make sure it handles past context correctly
query = 'stream:Denmark pm-with:';
suggestions = search.get_suggestions(query);
expected = [
'stream:Denmark pm-with:',
'stream:Denmark',
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted@zulip.com sender:';
suggestions = search.get_suggestions(query);
expected = [
'sender:ted@zulip.com sender:',
'sender:ted@zulip.com',
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_group_suggestions() {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return undefined;
};
set_global('activity', {
get_huddles: function () {
return [];
},
});
var ted =
{
email: 'ted@zulip.com',
user_id: 101,
full_name: 'Ted Smith',
};
var alice =
{
email: 'alice@zulip.com',
user_id: 102,
full_name: 'Alice Ignore',
};
var jeff =
{
email: 'jeff@zulip.com',
user_id: 103,
full_name: 'Jeff Zoolipson',
};
people.add(ted);
people.add(alice);
people.add(jeff);
// Entering a comma in a pm-with query should immediately generate
// suggestions for the next person.
var query = 'pm-with:bob@zulip.com,';
var suggestions = search.get_suggestions(query);
var expected = [
"pm-with:bob@zulip.com,",
"pm-with:bob@zulip.com,alice@zulip.com",
"pm-with:bob@zulip.com,jeff@zulip.com",
"pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Only the last part of a comma-separated pm-with query should be used to
// generate suggestions.
query = 'pm-with:bob@zulip.com,t';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:bob@zulip.com,t",
"pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Smit should also generate ted@zulip.com (Ted Smith) as a suggestion.
query = 'pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:bob@zulip.com,Smit",
"pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Do not suggest "bob@zulip.com" (the name of the current user)
query = 'pm-with:ted@zulip.com,bo';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:ted@zulip.com,bo",
];
assert.deepEqual(suggestions.strings, expected);
// No superfluous suggestions should be generated.
query = 'pm-with:bob@zulip.com,red';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:bob@zulip.com,red",
];
assert.deepEqual(suggestions.strings, expected);
// is:private should be properly prepended to each suggestion if the pm-with
// operator is negated.
query = '-pm-with:bob@zulip.com,';
suggestions = search.get_suggestions(query);
expected = [
"-pm-with:bob@zulip.com,",
"is:private -pm-with:bob@zulip.com,alice@zulip.com",
"is:private -pm-with:bob@zulip.com,jeff@zulip.com",
"is:private -pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:bob@zulip.com,t';
suggestions = search.get_suggestions(query);
expected = [
"-pm-with:bob@zulip.com,t",
"is:private -pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions(query);
expected = [
"-pm-with:bob@zulip.com,Smit",
"is:private -pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:bob@zulip.com,red';
suggestions = search.get_suggestions(query);
expected = [
"-pm-with:bob@zulip.com,red",
];
assert.deepEqual(suggestions.strings, expected);
// Test multiple operators
query = 'is:starred has:link pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions(query);
expected = [
"is:starred has:link pm-with:bob@zulip.com,Smit",
"is:starred has:link pm-with:bob@zulip.com,ted@zulip.com",
"is:starred has:link",
"is:starred",
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark has:link pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions(query);
expected = [
"stream:Denmark has:link pm-with:bob@zulip.com,Smit",
"stream:Denmark has:link",
"stream:Denmark",
];
assert.deepEqual(suggestions.strings, expected);
set_global('activity', {
get_huddles: function () {
return ['101,42', '101,103,42'];
},
});
// Simulate a past huddle which should now prioritize ted over alice
query = 'pm-with:bob@zulip.com,';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:bob@zulip.com,",
"pm-with:bob@zulip.com,ted@zulip.com",
"pm-with:bob@zulip.com,alice@zulip.com",
"pm-with:bob@zulip.com,jeff@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// bob,ted,jeff is already an existing huddle, so prioritize this one
query = 'pm-with:bob@zulip.com,ted@zulip.com,';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:bob@zulip.com,ted@zulip.com,",
"pm-with:bob@zulip.com,ted@zulip.com,jeff@zulip.com",
"pm-with:bob@zulip.com,ted@zulip.com,alice@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// bob,ted,jeff is already an existing huddle, but if we start with just bob,
// then don't prioritize ted over alice because it doesn't complete the full huddle.
query = 'pm-with:jeff@zulip.com,';
suggestions = search.get_suggestions(query);
expected = [
"pm-with:jeff@zulip.com,",
"pm-with:jeff@zulip.com,alice@zulip.com",
"pm-with:jeff@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
}());
init();
(function test_empty_query_suggestions() {
var query = '';
global.stream_data.subscribed_streams = function () {
return ['devel', 'office'];
};
global.narrow_state.stream = function () {
return undefined;
};
var suggestions = search.get_suggestions(query);
var expected = [
"",
"is:private",
"is:starred",
"is:mentioned",
"is:alerted",
"is:unread",
"sender:bob@zulip.com",
"stream:devel",
"stream:office",
];
assert.deepEqual(suggestions.strings, expected);
function describe(q) {
return suggestions.lookup_table[q].description;
}
assert.equal(describe('is:private'), 'Private 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('sender:bob@zulip.com'), 'Sent by me');
}());
(function test_sent_by_me_suggestions() {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return undefined;
};
var query = '';
var suggestions = search.get_suggestions(query);
assert(suggestions.strings.indexOf('sender:bob@zulip.com') !== -1);
assert.equal(suggestions.lookup_table['sender:bob@zulip.com'].description,
'Sent by me');
query = 'sender';
suggestions = search.get_suggestions(query);
var expected = [
"sender",
"sender:bob@zulip.com",
"sender:",
];
assert.deepEqual(suggestions.strings, expected);
query = 'from';
suggestions = search.get_suggestions(query);
expected = [
"from",
"from:bob@zulip.com",
"from:",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:bob@zulip.com';
suggestions = search.get_suggestions(query);
expected = [
"sender:bob@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'from:bob@zulip.com';
suggestions = search.get_suggestions(query);
expected = [
"from:bob@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sent';
suggestions = search.get_suggestions(query);
expected = [
"sent",
"sender:bob@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark topic:Denmark1 sent';
suggestions = search.get_suggestions(query);
expected = [
"stream:Denmark topic:Denmark1 sent",
"stream:Denmark topic:Denmark1 sender:bob@zulip.com",
"stream:Denmark topic:Denmark1",
"stream:Denmark",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:starred sender:m';
suggestions = search.get_suggestions(query);
expected = [
"is:starred sender:m",
"is:starred sender:bob@zulip.com",
"is:starred",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:alice@zulip.com sender:';
suggestions = search.get_suggestions(query);
expected = [
"sender:alice@zulip.com sender:",
"sender:alice@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_topic_suggestions() {
var suggestions;
var expected;
global.stream_data.subscribed_streams = function () {
return ['office'];
};
global.narrow_state.stream = function () {
return 'office';
};
var devel_id = 44;
var office_id = 77;
global.stream_data.get_stream_id = function (stream_name) {
switch (stream_name) {
case 'office': return office_id;
case 'devel': return devel_id;
}
};
topic_data.reset();
suggestions = search.get_suggestions('te');
expected = [
"te",
];
assert.deepEqual(suggestions.strings, expected);
topic_data.add_message({
stream_id: devel_id,
topic_name: 'REXX',
});
_.each(['team', 'ignore', 'test'], function (topic_name) {
topic_data.add_message({
stream_id: office_id,
topic_name: topic_name,
});
});
suggestions = search.get_suggestions('te');
expected = [
"te",
"stream:office topic:team",
"stream:office topic:test",
];
assert.deepEqual(suggestions.strings, expected);
function describe(q) {
return suggestions.lookup_table[q].description;
}
assert.equal(describe('te'), "Search for te");
assert.equal(describe('stream:office topic:team'), "Stream office > team");
suggestions = search.get_suggestions('topic:staplers stream:office');
expected = [
'topic:staplers stream:office',
'topic:staplers',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions('stream:devel topic:');
expected = [
'stream:devel topic:',
'stream:devel topic:REXX',
'stream:devel',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions('stream:devel -topic:');
expected = [
'stream:devel -topic:',
'stream:devel -topic:REXX',
'stream:devel',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions('-topic:te');
expected = [
'-topic:te',
'stream:office -topic:team',
'stream:office -topic:test',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions('is:alerted stream:devel is:starred topic:');
expected = [
'is:alerted stream:devel is:starred topic:',
'is:alerted stream:devel is:starred topic:REXX',
'is:alerted stream:devel is:starred',
'is:alerted stream:devel',
'is:alerted',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions('is:private stream:devel topic:');
expected = [
'is:private stream:devel topic:',
'is:private stream:devel',
'is:private',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions('topic:REXX stream:devel topic:');
expected = [
'topic:REXX stream:devel topic:',
'topic:REXX stream:devel',
'topic:REXX',
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_whitespace_glitch() {
var query = 'stream:office '; // note trailing space
global.stream_data.subscribed_streams = function () {
return ['office'];
};
global.narrow_state.stream = function () {
return;
};
topic_data.reset();
var suggestions = search.get_suggestions(query);
var expected = [
"stream:office",
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_stream_completion() {
global.stream_data.subscribed_streams = function () {
return ['office', 'dev help'];
};
global.narrow_state.stream = function () {
return;
};
topic_data.reset();
var query = 'stream:of';
var suggestions = search.get_suggestions(query);
var expected = [
"stream:of",
"stream:office",
];
assert.deepEqual(suggestions.strings, expected);
query = 'hel';
suggestions = search.get_suggestions(query);
expected = [
"hel",
"stream:dev+help",
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_people_suggestions() {
var query = 'te';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
var ted = {
email: 'ted@zulip.com',
user_id: 201,
full_name: 'Ted Smith',
};
var bob = {
email: 'bob@zulip.com',
user_id: 202,
full_name: 'Bob Térry',
};
var alice = {
email: 'alice@zulip.com',
user_id: 203,
full_name: 'Alice Ignore',
};
people.add(ted);
people.add(bob);
people.add(alice);
topic_data.reset();
var suggestions = search.get_suggestions(query);
var expected = [
"te",
"pm-with:bob@zulip.com", // bob térry
"pm-with:ted@zulip.com",
"sender:bob@zulip.com",
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
function describe(q) {
return suggestions.lookup_table[q].description;
}
assert.equal(describe('pm-with:ted@zulip.com'),
"Private messages with Ted Smith <ted@zulip.com>");
assert.equal(describe('sender:ted@zulip.com'),
"Sent by Ted Smith <ted@zulip.com>");
suggestions = search.get_suggestions('Ted '); // note space
expected = [
"Ted",
"pm-with:ted@zulip.com",
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_contains_suggestions() {
var query = 'has:';
var suggestions = search.get_suggestions(query);
var expected = [
'has:',
'has:link',
'has:image',
'has:attachment',
];
assert.deepEqual(suggestions.strings, expected);
query = 'has:im';
suggestions = search.get_suggestions(query);
expected = [
'has:im',
'has:image',
];
assert.deepEqual(suggestions.strings, expected);
query = '-has:im';
suggestions = search.get_suggestions(query);
expected = [
'-has:im',
'-has:image',
];
assert.deepEqual(suggestions.strings, expected);
query = 'att';
suggestions = search.get_suggestions(query);
expected = [
'att',
'has:attachment',
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark is:alerted has:lin';
suggestions = search.get_suggestions(query);
expected = [
'stream:Denmark is:alerted has:lin',
'stream:Denmark is:alerted has:link',
'stream:Denmark is:alerted',
'stream:Denmark',
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_operator_suggestions() {
// Completed operator should return nothing
var query = 'stream:';
var suggestions = search.get_suggestions(query);
var expected = [
'stream:',
];
assert.deepEqual(suggestions.strings, expected);
query = 'st';
suggestions = search.get_suggestions(query);
expected = [
'st',
'is:starred',
'stream:',
];
assert.deepEqual(suggestions.strings, expected);
query = '-s';
suggestions = search.get_suggestions(query);
expected = [
'-s',
'-stream:',
'-sender:',
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark is:alerted -f';
suggestions = search.get_suggestions(query);
expected = [
'stream:Denmark is:alerted -f',
'stream:Denmark is:alerted -from:',
'stream:Denmark is:alerted',
'stream:Denmark',
];
assert.deepEqual(suggestions.strings, expected);
}());
(function test_queries_with_spaces() {
global.stream_data.subscribed_streams = function () {
return ['office', 'dev help'];
};
global.narrow_state.stream = function () {
return;
};
topic_data.reset();
// test allowing spaces with quotes surrounding operand
var query = 'stream:"dev he"';
var suggestions = search.get_suggestions(query);
var expected = [
"stream:dev+he",
"stream:dev+help",
];
assert.deepEqual(suggestions.strings, expected);
// test mismatched quote
query = 'stream:"dev h';
suggestions = search.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 = search.get_suggestions(query);
expected = [
"stream:offi",
"stream:office",
];
assert.deepEqual(suggestions.strings, expected);
}());