zulip/web/tests/search_suggestion_future.te...

1021 lines
33 KiB
JavaScript
Raw Normal View History

"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");
2020-08-20 21:24:06 +02:00
const people = zrequire("people");
const search = zrequire("search_suggestion");
search.__Rewire__("max_num_of_search_results", 15);
search: Retrofit recent changes to pills code. This change makes these two functions more alike: - get_search_result - get_search_result_legacy To test the UI modify zerver/views/home.py by replacing `settings.SEARCH_PILLS_ENABLED` with `True`. I only did a quick sanity check, since any bugs with the new system are more likely due to bitrot than any changes I have made here. The history is this: Tim cloned the code (before the smaller helpers were extracted): db4f6e278f5600e89a2da8d626b38943d3977c59 In 8b153f6452b178929d90fb6820e68a2f29c6e6da Shubham removed get_operator_subset_suggestions but accidentally left a `concat` statement in that got misapplied to the previous suggestions: - suggestions = get_operator_subset_suggestions(operators); result = result.concat(suggestions); The error there was carried over in some recent changes, but this commit fixes that strangeness. In 73e4f3b3fad4b20abc7a3d458950564094a81a2b Shubham made this change, which makes sense only for pills, and this code remains intact. - if (operators.length > 0) { - last = operators.slice(-1)[0]; + if (query_operators.length > 0) { + last = query_operators.slice(-1)[0]; + } else { + // If query_operators = [] then last will remain + // {operator: '', operand: '', negated: false}; from above. + // `last` has not yet been added to operators/query_operators. + // The code below adds last to operators/query_operators + operators.push(last); + query_operators.push(last); } Mohit made a couple changes to both old and new. Anders made a couple non-substantive changes related to the ES6 migration. Steve (me) made several structural changes to the code. For some of them I only changed the legacy code, not the pills code. I didn't fix Shubham's mistake until this change. Now the two functions should look similar except in the places where they are intentionally different. I also added a comment explaining the get_operator_subset_suggestions difference. Fixes #13609
2020-01-07 14:21:35 +01:00
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,
});
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",
"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 &gt; 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 = "<strong>Te</strong>d 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);
});