zulip/frontend_tests/node_tests/search_suggestion.js

1218 lines
33 KiB
JavaScript
Raw Normal View History

set_global('page_params', {
search_pills_enabled: true,
});
set_global('message_store', {
user_ids: () => [],
});
const settings_config = zrequire('settings_config');
page_params.realm_email_address_visibility =
settings_config.email_address_visibility_values.admins_only.code;
const huddle_data = zrequire('huddle_data');
zrequire('typeahead_helper');
set_global('Handlebars', global.make_handlebars());
zrequire('Filter', 'js/filter');
zrequire('narrow_state');
zrequire('stream_data');
zrequire('stream_topic_history');
zrequire('people');
zrequire('unread');
zrequire('common');
const search = zrequire('search_suggestion');
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
search.max_num_of_search_results = 15;
const me = {
email: 'myself@zulip.com',
full_name: 'Me Myself',
user_id: 41,
};
const bob = {
email: 'bob@zulip.com',
full_name: 'Bob Roberts',
user_id: 42,
};
const ted =
{
email: 'ted@zulip.com',
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 = () => {};
function init() {
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);
}
init();
page_params.is_admin = true;
set_global('narrow', {});
stream_topic_history.reset();
function get_suggestions(base_query, query) {
return search.get_suggestions(base_query, query);
}
run_test('basic_get_suggestions', () => {
const query = 'fred';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return 'office';
};
const suggestions = get_suggestions('', query);
const expected = [
'fred',
];
assert.deepEqual(suggestions.strings, expected);
});
run_test('subset_suggestions', () => {
const query = 'shakespeare';
const base_query = 'stream:Denmark topic:Hamlet';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
const suggestions = get_suggestions(base_query, query);
const expected = [
"shakespeare",
];
assert.deepEqual(suggestions.strings, expected);
});
run_test('private_suggestions', () => {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
let query = 'is:private';
let suggestions = get_suggestions('', query);
let expected = [
"is:private",
"pm-with:alice@zulip.com",
"pm-with:bob@zulip.com",
"pm-with:jeff@zulip.com",
"pm-with:myself@zulip.com",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'al';
let base_query = 'is:private';
suggestions = get_suggestions(base_query, query);
expected = [
"al",
"is:alerted",
"sender:alice@zulip.com",
"pm-with:alice@zulip.com",
"group-pm-with:alice@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:t';
suggestions = get_suggestions('', query);
expected = [
"pm-with:t",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:t';
suggestions = 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 = get_suggestions('', query);
expected = [
"pm-with: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:private';
suggestions = get_suggestions(base_query, query);
expected = [
"near:3",
];
assert.deepEqual(suggestions.strings, expected);
query = 'near:3';
base_query = 'pm-with: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:private';
suggestions = get_suggestions(base_query, query);
expected = [
"al",
"is:alerted",
"sender:alice@zulip.com",
"pm-with:alice@zulip.com",
"group-pm-with:alice@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Make sure it handles past context correctly
query = 'pm-with:';
base_query = 'stream:Denmark';
suggestions = get_suggestions(base_query, query);
expected = [
'pm-with:',
];
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);
});
run_test('group_suggestions', () => {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
// Entering a comma in a pm-with query should immediately generate
// suggestions for the next person.
let query = 'pm-with:bob@zulip.com,';
let suggestions = get_suggestions('', query);
let 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 = 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 = 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 "myself@zulip.com" (the name of the current user)
query = 'pm-with:ted@zulip.com,mys';
suggestions = get_suggestions('', query);
expected = [
"pm-with:ted@zulip.com,mys",
];
assert.deepEqual(suggestions.strings, expected);
// No superfluous suggestions should be generated.
query = 'pm-with:bob@zulip.com,red';
suggestions = 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 = 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 = 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 = 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 = get_suggestions('', query);
expected = [
"-pm-with:bob@zulip.com,red",
];
assert.deepEqual(suggestions.strings, expected);
// Test multiple operators
query = 'pm-with:bob@zulip.com,Smit';
let base_query = 'is:starred has:link';
suggestions = get_suggestions(base_query, query);
expected = [
"pm-with:bob@zulip.com,Smit",
"pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:bob@zulip.com,Smit';
base_query = 'stream:Denmark has:link';
suggestions = get_suggestions(base_query, query);
expected = [
"pm-with:bob@zulip.com,Smit",
];
assert.deepEqual(suggestions.strings, expected);
function message(user_ids, timestamp) {
return {
type: 'private',
display_recipient: user_ids.map((id) => ({
id: id,
})),
timestamp: 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 huddle which should now prioritize ted over alice
query = 'pm-with:bob@zulip.com,';
suggestions = 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 = 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 jeff,
// then don't prioritize ted over alice because it doesn't complete the full huddle.
query = 'pm-with:jeff@zulip.com,';
suggestions = get_suggestions('', query);
expected = [
"pm-with:jeff@zulip.com,",
"pm-with:jeff@zulip.com,alice@zulip.com",
"pm-with:jeff@zulip.com,bob@zulip.com",
"pm-with:jeff@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = "pm-with:jeff@zulip.com,ted@zulip.com hi";
suggestions = get_suggestions("", query);
expected = [
"pm-with:jeff@zulip.com,ted@zulip.com hi",
];
assert.deepEqual(suggestions.strings, expected);
});
init();
run_test('empty_query_suggestions', () => {
const query = '';
global.stream_data.subscribed_streams = function () {
return ['devel', 'office'];
};
global.narrow_state.stream = function () {
return;
};
const suggestions = get_suggestions('', query);
const expected = [
"",
"streams:public",
"is:private",
"is:starred",
"is:mentioned",
"is:alerted",
"is:unread",
"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;
}
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: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');
});
run_test('has_suggestions', () => {
// Checks that category wise suggestions are displayed instead of a single
// default suggestion when suggesting `has` operator.
let query = 'h';
global.stream_data.subscribed_streams = function () {
return ['devel', 'office'];
};
global.narrow_state.stream = function () {
return;
};
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;
}
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);
});
run_test('check_is_suggestions', () => {
let query = 'i';
global.stream_data.subscribed_streams = function () {
return ['devel', 'office'];
};
global.narrow_state.stream = function () {
return;
};
let suggestions = get_suggestions('', query);
let expected = [
'i',
'is:private',
'is:starred',
'is:mentioned',
'is:alerted',
'is:unread',
'sender:alice@zulip.com',
'pm-with:alice@zulip.com',
'group-pm-with:alice@zulip.com',
'has:image',
];
assert.deepEqual(suggestions.strings, expected);
function describe(q) {
return suggestions.lookup_table.get(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');
query = '-i';
suggestions = get_suggestions('', query);
expected = [
'-i',
'-is:private',
'-is:starred',
'-is:mentioned',
'-is:alerted',
'-is:unread',
];
assert.deepEqual(suggestions.strings, expected);
assert.equal(describe('-is:private'), 'Exclude private 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');
query = '';
suggestions = get_suggestions('', query);
expected = [
'',
'streams:public',
'is:private',
'is:starred',
'is:mentioned',
'is:alerted',
'is:unread',
'sender:myself@zulip.com',
'stream:devel',
'stream:office',
'has:link',
'has:image',
'has:attachment',
];
assert.deepEqual(suggestions.strings, expected);
query = '';
let base_query = 'is:private';
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:private',
'is:starred',
'is:mentioned',
'is:alerted',
'is:unread',
];
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);
});
run_test('sent_by_me_suggestions', () => {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
let query = '';
let suggestions = get_suggestions('', query);
assert(suggestions.strings.includes('sender:myself@zulip.com'));
assert.equal(suggestions.lookup_table.get('sender:myself@zulip.com').description,
'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);
});
run_test('topic_suggestions', () => {
let suggestions;
let expected;
global.stream_data.subscribed_streams = function () {
return ['office'];
};
global.narrow_state.stream = function () {
return 'office';
};
const devel_id = 44;
const 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;
}
};
stream_topic_history.reset();
suggestions = get_suggestions('', 'te');
expected = [
'te',
'sender:ted@zulip.com',
'pm-with:ted@zulip.com',
'group-pm-with:ted@zulip.com',
];
assert.deepEqual(suggestions.strings, expected);
stream_topic_history.add_message({
stream_id: devel_id,
topic_name: 'REXX',
});
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
for (const topic_name of ['team', 'ignore', 'test']) {
stream_topic_history.add_message({
stream_id: office_id,
topic_name: 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',
'pm-with:ted@zulip.com',
'group-pm-with: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;
}
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:private 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);
});
run_test('whitespace_glitch', () => {
const query = 'stream:office '; // note trailing space
global.stream_data.subscribed_streams = function () {
return ['office'];
};
global.narrow_state.stream = function () {
return;
};
stream_topic_history.reset();
const suggestions = get_suggestions('', query);
const expected = [
"stream:office",
];
assert.deepEqual(suggestions.strings, expected);
});
run_test('stream_completion', () => {
global.stream_data.subscribed_streams = function () {
return ['office', 'dev help'];
};
global.narrow_state.stream = function () {
return;
};
stream_topic_history.reset();
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() {
global.stream_data.subscribed_streams = () => [];
global.narrow_state.stream = noop;
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',
};
people.add_active_user(bob);
const alice = {
email: 'alice@zulip.com',
user_id: 203,
full_name: 'Alice Ignore',
};
people.add_active_user(alice);
stream_topic_history.reset();
}
run_test('people_suggestions', () => {
people_suggestion_setup();
let query = 'te';
let suggestions = get_suggestions('', query);
let expected = [
"te",
"sender:bob@zulip.com",
"sender:ted@zulip.com",
"pm-with:bob@zulip.com", // bob térry
"pm-with:ted@zulip.com",
"group-pm-with:bob@zulip.com",
"group-pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
const describe = (q) => suggestions.lookup_table.get(q).description;
assert.equal(describe('pm-with:ted@zulip.com'),
"Private messages with <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;");
assert.equal(describe('sender:ted@zulip.com'),
"Sent by <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;");
suggestions = get_suggestions('', 'Ted '); // note space
expected = [
"Ted",
"sender:ted@zulip.com",
"pm-with:ted@zulip.com",
"group-pm-with: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);
});
run_test('people_suggestion (Admin only email visibility)', () => {
/* Suggestions when realm_email_address_visibility is set to admin
only */
people_suggestion_setup();
const query = 'te';
page_params.is_admin = false;
const suggestions = get_suggestions('', query);
const expected = [
"te",
"sender:bob@zulip.com",
"sender:ted@zulip.com",
"pm-with:bob@zulip.com", // bob térry
"pm-with:ted@zulip.com",
"group-pm-with:bob@zulip.com",
"group-pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
const describe = (q) => suggestions.lookup_table.get(q).description;
assert.equal(describe('pm-with:ted@zulip.com'),
'Private messages with <strong>Te</strong>d Smith');
assert.equal(describe('sender:ted@zulip.com'),
'Sent by <strong>Te</strong>d Smith');
});
run_test('operator_suggestions', () => {
// 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 = 'group-';
suggestions = get_suggestions('', query);
expected = [
'group-',
'group-pm-with:',
];
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);
});
run_test('queries_with_spaces', () => {
global.stream_data.subscribed_streams = function () {
return ['office', 'dev help'];
};
global.narrow_state.stream = function () {
return;
};
stream_topic_history.reset();
// 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.
run_test('multiple_operators_without_pills', () => {
let query = 'is:private al';
let base_query = '';
let suggestions = get_suggestions(base_query, query);
let expected = [
"is:private al",
"is:private is:alerted",
"is:private sender:alice@zulip.com",
"is:private pm-with:alice@zulip.com",
"is:private group-pm-with: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 = [
"is:alerted sender:ted@zulip.com abc",
];
assert.deepEqual(suggestions.strings, expected);
});