zulip/web/tests/search_suggestion.test.js

1041 lines
34 KiB
JavaScript
Raw Normal View History

"use strict";
const assert = require("node:assert/strict");
const {mock_esm, zrequire} = require("./lib/namespace");
const {run_test, noop} = require("./lib/test");
const {current_user, page_params, realm} = 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 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");
2020-08-20 21:24:06 +02:00
const people = zrequire("people");
const search = zrequire("search_suggestion");
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() {
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));
search suggestions: De-duplicate legacy search codepath. This merges the `exports.get_search_result_legacy` and `exports.get_search_result` function. The key differences between the two code paths are as follows: * We only want to generate suggestions for the queries which the user is typing or can edit. For the legacy version, suggestions are displayed for the entire search string in the searchbox. (`all_operators`) For the pills enabled version, suggestions are displayed only for the input which hasn't been converted to pills. (`query_operators`) `all_operators` = `base_query_operators` + " " + `query_operators`. trim is added at the end just to handle the legacy case where we pass the `base_query` as ''. * It is not possible to detect whether the user wants to continue typing in the legacy version. However if the the searchbox is still focused even after pill creation we can assume the user still wants to continue typing. To handle this we push an empty term as the `last` operator. This is possible since the previous queries have been completely entered as evident from it's generated pill. * When using the legacy version, `search_operators` are the same as `all_operators`, as mentioned in point 1. In the pills enabled version we perform most of the computations from the `query_operators`, but we do require all `all_operators`, only for filtering the last query's suggestion. * And there is just one block unique to the legacy search system. More details are mentioned in the comments of that block. We also refactor both the search suggestions node tests, mainly to make them similar and easier to detect differences when we switch over to the new version.
2020-06-01 15:00:42 +02:00
}
function test(label, f) {
run_test(label, (helpers) => {
init();
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,
});
js: Automatically convert _.each to for…of. This commit was automatically generated by the following script, followed by lint --fix and a few small manual lint-related cleanups. import * as babelParser from "recast/parsers/babel"; import * as recast from "recast"; import * as tsParser from "recast/parsers/typescript"; import { builders as b, namedTypes as n } from "ast-types"; import { Context } from "ast-types/lib/path-visitor"; import K from "ast-types/gen/kinds"; import { NodePath } from "ast-types/lib/node-path"; import assert from "assert"; import fs from "fs"; import path from "path"; import process from "process"; const checkExpression = (node: n.Node): node is K.ExpressionKind => n.Expression.check(node); const checkStatement = (node: n.Node): node is K.StatementKind => n.Statement.check(node); for (const file of process.argv.slice(2)) { console.log("Parsing", file); const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), { parser: path.extname(file) === ".ts" ? tsParser : babelParser, }); let changed = false; let inLoop = false; let replaceReturn = false; const visitLoop = (...args: string[]) => function(this: Context, path: NodePath) { for (const arg of args) { this.visit(path.get(arg)); } const old = { inLoop }; inLoop = true; this.visit(path.get("body")); inLoop = old.inLoop; return false; }; recast.visit(ast, { visitDoWhileStatement: visitLoop("test"), visitExpressionStatement(path) { const { expression, comments } = path.node; let valueOnly; if ( n.CallExpression.check(expression) && n.MemberExpression.check(expression.callee) && !expression.callee.computed && n.Identifier.check(expression.callee.object) && expression.callee.object.name === "_" && n.Identifier.check(expression.callee.property) && ["each", "forEach"].includes(expression.callee.property.name) && [2, 3].includes(expression.arguments.length) && checkExpression(expression.arguments[0]) && (n.FunctionExpression.check(expression.arguments[1]) || n.ArrowFunctionExpression.check(expression.arguments[1])) && [1, 2].includes(expression.arguments[1].params.length) && n.Identifier.check(expression.arguments[1].params[0]) && ((valueOnly = expression.arguments[1].params[1] === undefined) || n.Identifier.check(expression.arguments[1].params[1])) && (expression.arguments[2] === undefined || n.ThisExpression.check(expression.arguments[2])) ) { const old = { inLoop, replaceReturn }; inLoop = false; replaceReturn = true; this.visit( path .get("expression") .get("arguments") .get(1) .get("body") ); inLoop = old.inLoop; replaceReturn = old.replaceReturn; const [right, { body, params }] = expression.arguments; const loop = b.forOfStatement( b.variableDeclaration("let", [ b.variableDeclarator( valueOnly ? params[0] : b.arrayPattern([params[1], params[0]]) ), ]), valueOnly ? right : b.callExpression( b.memberExpression(right, b.identifier("entries")), [] ), checkStatement(body) ? body : b.expressionStatement(body) ); loop.comments = comments; path.replace(loop); changed = true; } this.traverse(path); }, visitForStatement: visitLoop("init", "test", "update"), visitForInStatement: visitLoop("left", "right"), visitForOfStatement: visitLoop("left", "right"), visitFunction(path) { this.visit(path.get("params")); const old = { replaceReturn }; replaceReturn = false; this.visit(path.get("body")); replaceReturn = old.replaceReturn; return false; }, visitReturnStatement(path) { if (replaceReturn) { assert(!inLoop); // could use labeled continue if this ever fires const { argument, comments } = path.node; if (argument === null) { const s = b.continueStatement(); s.comments = comments; path.replace(s); } else { const s = b.expressionStatement(argument); s.comments = comments; path.replace(s, b.continueStatement()); } return false; } this.traverse(path); }, visitWhileStatement: visitLoop("test"), }); if (changed) { console.log("Writing", file); fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" }); } } Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
}
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 <strong>te</strong>");
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 = "<strong>Te</strong>d 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 = "<i>(guest)</i>";
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));
}
}
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);
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);
});