zulip/frontend_tests/node_tests/search_suggestion_legacy.js

1135 lines
31 KiB
JavaScript
Raw Normal View History

set_global('page_params', {
search_pills_enabled: false,
});
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;
zrequire('typeahead_helper');
set_global('Handlebars', global.make_handlebars());
zrequire('Filter', 'js/filter');
zrequire('narrow_state');
zrequire('stream_data');
zrequire('topic_data');
zrequire('people');
zrequire('unread');
zrequire('common');
const search = zrequire('search_suggestion');
search.max_num_of_search_results = 15;
const bob = {
email: 'bob@zulip.com',
full_name: 'Bob Roberts',
user_id: 42,
};
function init() {
people.init();
people.add(bob);
people.initialize_current_user(bob.user_id);
}
init();
set_global('narrow', {});
page_params.is_admin = true;
topic_data.reset();
run_test('basic_get_suggestions', () => {
const query = 'fred';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return 'office';
};
const suggestions = search.get_suggestions_legacy(query);
const expected = [
'fred',
];
assert.deepEqual(suggestions.strings, expected);
});
run_test('subset_suggestions', () => {
const query = 'stream:Denmark topic:Hamlet shakespeare';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
const suggestions = search.get_suggestions_legacy(query);
const expected = [
"stream:Denmark topic:Hamlet shakespeare",
"stream:Denmark topic:Hamlet",
"stream:Denmark",
];
assert.deepEqual(suggestions.strings, expected);
});
run_test('private_suggestions', () => {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
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',
};
people.add(ted);
people.add(alice);
let query = 'is:private';
let suggestions = search.get_suggestions_legacy(query);
let expected = [
"is:private",
"pm-with:alice@zulip.com",
"pm-with:bob@zulip.com",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:private al';
suggestions = search.get_suggestions_legacy(query);
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",
"is:private",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:t';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:t",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:t';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-pm-with:t",
"is:private -pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:ted@zulip.com';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted';
suggestions = search.get_suggestions_legacy(query);
expected = [
"sender:ted",
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:te';
suggestions = search.get_suggestions_legacy(query);
expected = [
"sender:te",
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-sender:te';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-sender:te",
"-sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted@zulip.com';
suggestions = search.get_suggestions_legacy(query);
expected = [
"sender:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:unread from:ted';
suggestions = search.get_suggestions_legacy(query);
expected = [
"is:unread from:ted",
"is:unread from:ted@zulip.com",
"is:unread",
];
assert.deepEqual(suggestions.strings, expected);
// Users can enter bizarre queries, and if they do, we want to
// be conservative with suggestions.
query = 'is:private near:3';
suggestions = search.get_suggestions_legacy(query);
expected = [
"is:private near:3",
"is:private",
];
assert.deepEqual(suggestions.strings, expected);
query = 'pm-with:ted@zulip.com near:3';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:ted@zulip.com near:3",
"pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Make sure suggestions still work if preceding tokens
query = 'is:alerted sender:ted@zulip.com';
suggestions = search.get_suggestions_legacy(query);
expected = [
"is:alerted sender:ted@zulip.com",
"is:alerted",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:starred has:link is:private al';
suggestions = search.get_suggestions_legacy(query);
expected = [
"is:starred has:link is:private al",
"is:starred has:link is:private is:alerted",
"is:starred has:link is:private sender:alice@zulip.com",
"is:starred has:link is:private pm-with:alice@zulip.com",
"is:starred has:link is:private group-pm-with:alice@zulip.com",
"is:starred has:link is:private",
"is:starred has:link",
"is:starred",
];
assert.deepEqual(suggestions.strings, expected);
// Make sure it handles past context correctly
query = 'stream:Denmark pm-with:';
suggestions = search.get_suggestions_legacy(query);
expected = [
'stream:Denmark pm-with:',
'stream:Denmark',
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted@zulip.com sender:';
suggestions = search.get_suggestions_legacy(query);
expected = [
'sender:ted@zulip.com sender:',
'sender:ted@zulip.com',
];
assert.deepEqual(suggestions.strings, expected);
});
run_test('group_suggestions', () => {
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
set_global('activity', {
get_huddles: function () {
return [];
},
});
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',
};
people.add(ted);
people.add(alice);
people.add(jeff);
// Entering a comma in a pm-with query should immediately generate
// suggestions for the next person.
let query = 'pm-with:bob@zulip.com,';
let suggestions = search.get_suggestions_legacy(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 = search.get_suggestions_legacy(query);
expected = [
"pm-with:bob@zulip.com,t",
"pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Smit should also generate ted@zulip.com (Ted Smith) as a suggestion.
query = 'pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:bob@zulip.com,Smit",
"pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// Do not suggest "bob@zulip.com" (the name of the current user)
query = 'pm-with:ted@zulip.com,bo';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:ted@zulip.com,bo",
];
assert.deepEqual(suggestions.strings, expected);
// No superfluous suggestions should be generated.
query = 'pm-with:bob@zulip.com,red';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:bob@zulip.com,red",
];
assert.deepEqual(suggestions.strings, expected);
// is:private should be properly prepended to each suggestion if the pm-with
// operator is negated.
query = '-pm-with:bob@zulip.com,';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-pm-with:bob@zulip.com,",
"is:private -pm-with:bob@zulip.com,alice@zulip.com",
"is:private -pm-with:bob@zulip.com,jeff@zulip.com",
"is:private -pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:bob@zulip.com,t';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-pm-with:bob@zulip.com,t",
"is:private -pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-pm-with:bob@zulip.com,Smit",
"is:private -pm-with:bob@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-pm-with:bob@zulip.com,red';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-pm-with:bob@zulip.com,red",
];
assert.deepEqual(suggestions.strings, expected);
// Test multiple operators
query = 'is:starred has:link pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions_legacy(query);
expected = [
"is:starred has:link pm-with:bob@zulip.com,Smit",
"is:starred has:link pm-with:bob@zulip.com,ted@zulip.com",
"is:starred has:link",
"is:starred",
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark has:link pm-with:bob@zulip.com,Smit';
suggestions = search.get_suggestions_legacy(query);
expected = [
"stream:Denmark has:link pm-with:bob@zulip.com,Smit",
"stream:Denmark has:link",
"stream:Denmark",
];
assert.deepEqual(suggestions.strings, expected);
set_global('activity', {
get_huddles: function () {
return ['101,42', '101,103,42'];
},
});
// Simulate a past huddle which should now prioritize ted over alice
query = 'pm-with:bob@zulip.com,';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:bob@zulip.com,",
"pm-with:bob@zulip.com,ted@zulip.com",
"pm-with:bob@zulip.com,alice@zulip.com",
"pm-with:bob@zulip.com,jeff@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// bob,ted,jeff is already an existing huddle, so prioritize this one
query = 'pm-with:bob@zulip.com,ted@zulip.com,';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:bob@zulip.com,ted@zulip.com,",
"pm-with:bob@zulip.com,ted@zulip.com,jeff@zulip.com",
"pm-with:bob@zulip.com,ted@zulip.com,alice@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
// bob,ted,jeff is already an existing huddle, but if we start with just bob,
// then don't prioritize ted over alice because it doesn't complete the full huddle.
query = 'pm-with:jeff@zulip.com,';
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:jeff@zulip.com,",
"pm-with:jeff@zulip.com,alice@zulip.com",
"pm-with:jeff@zulip.com,ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = "pm-with:jeff@zulip.com,ted@zulip.com hi";
suggestions = search.get_suggestions_legacy(query);
expected = [
"pm-with:jeff@zulip.com,ted@zulip.com hi",
"pm-with:jeff@zulip.com,ted@zulip.com",
];
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 = search.get_suggestions_legacy(query);
const expected = [
"",
"streams:public",
"is:private",
"is:starred",
"is:mentioned",
"is:alerted",
"is:unread",
"sender:bob@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:bob@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 = search.get_suggestions_legacy(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 = search.get_suggestions_legacy(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 = search.get_suggestions_legacy(query);
expected = [
'has:link',
'has:image',
'has:attachment',
];
assert.deepEqual(suggestions.strings, expected);
query = 'has:im';
suggestions = search.get_suggestions_legacy(query);
expected = [
'has:image',
];
assert.deepEqual(suggestions.strings, expected);
query = '-has:im';
suggestions = search.get_suggestions_legacy(query);
expected = [
'-has:image',
];
assert.deepEqual(suggestions.strings, expected);
query = 'att';
suggestions = search.get_suggestions_legacy(query);
expected = [
'att',
'has:attachment',
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark is:alerted has:lin';
suggestions = search.get_suggestions_legacy(query);
expected = [
'stream:Denmark is:alerted has:link',
'stream:Denmark is:alerted',
'stream:Denmark',
];
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 = search.get_suggestions_legacy(query);
let expected = [
'i',
'is:private',
'is:starred',
'is:mentioned',
'is:alerted',
'is:unread',
'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 = search.get_suggestions_legacy(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');
// operand suggestions follow.
query = 'is:';
suggestions = search.get_suggestions_legacy(query);
expected = [
'is:private',
'is:starred',
'is:mentioned',
'is:alerted',
'is:unread',
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:st';
suggestions = search.get_suggestions_legacy(query);
expected = [
'is:starred',
];
assert.deepEqual(suggestions.strings, expected);
query = '-is:st';
suggestions = search.get_suggestions_legacy(query);
expected = [
'-is:starred',
];
assert.deepEqual(suggestions.strings, expected);
query = 'st';
suggestions = search.get_suggestions_legacy(query);
expected = [
'st',
'streams:public',
'is:starred',
'stream:',
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark has:link is:sta';
suggestions = search.get_suggestions_legacy(query);
expected = [
'stream:Denmark has:link is:starred',
'stream:Denmark has:link',
'stream:Denmark',
];
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 = search.get_suggestions_legacy(query);
js: Convert a.indexOf(…) !== -1 to a.includes(…). Babel polyfills this for us for Internet Explorer. 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 K from "ast-types/gen/kinds"; 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); 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; recast.visit(ast, { visitBinaryExpression(path) { const { operator, left, right } = path.node; if ( n.CallExpression.check(left) && n.MemberExpression.check(left.callee) && !left.callee.computed && n.Identifier.check(left.callee.property) && left.callee.property.name === "indexOf" && left.arguments.length === 1 && checkExpression(left.arguments[0]) && ((["===", "!==", "==", "!=", ">", "<="].includes(operator) && n.UnaryExpression.check(right) && right.operator == "-" && n.Literal.check(right.argument) && right.argument.value === 1) || ([">=", "<"].includes(operator) && n.Literal.check(right) && right.value === 0)) ) { const test = b.callExpression( b.memberExpression(left.callee.object, b.identifier("includes")), [left.arguments[0]] ); path.replace( ["!==", "!=", ">", ">="].includes(operator) ? test : b.unaryExpression("!", test) ); changed = true; } this.traverse(path); }, }); 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-08 04:55:06 +01:00
assert(suggestions.strings.includes('sender:bob@zulip.com'));
assert.equal(suggestions.lookup_table.get('sender:bob@zulip.com').description,
'Sent by me');
query = 'sender';
suggestions = search.get_suggestions_legacy(query);
let expected = [
"sender",
"sender:bob@zulip.com",
"sender:",
];
assert.deepEqual(suggestions.strings, expected);
query = '-sender';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-sender",
"-sender:bob@zulip.com",
"-sender:",
];
assert.deepEqual(suggestions.strings, expected);
query = 'from';
suggestions = search.get_suggestions_legacy(query);
expected = [
"from",
"from:bob@zulip.com",
"from:",
];
assert.deepEqual(suggestions.strings, expected);
query = '-from';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-from",
"-from:bob@zulip.com",
"-from:",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:bob@zulip.com';
suggestions = search.get_suggestions_legacy(query);
expected = [
"sender:bob@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'from:bob@zulip.com';
suggestions = search.get_suggestions_legacy(query);
expected = [
"from:bob@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sent';
suggestions = search.get_suggestions_legacy(query);
expected = [
"sent",
"sender:bob@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = '-sent';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-sent",
"-sender:bob@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark topic:Denmark1 sent';
suggestions = search.get_suggestions_legacy(query);
expected = [
"stream:Denmark topic:Denmark1 sent",
"stream:Denmark topic:Denmark1 sender:bob@zulip.com",
"stream:Denmark topic:Denmark1",
"stream:Denmark",
];
assert.deepEqual(suggestions.strings, expected);
query = 'is:starred sender:m';
suggestions = search.get_suggestions_legacy(query);
expected = [
"is:starred sender:m",
"is:starred sender:bob@zulip.com",
"is:starred",
];
assert.deepEqual(suggestions.strings, expected);
query = 'sender:alice@zulip.com sender:';
suggestions = search.get_suggestions_legacy(query);
expected = [
"sender:alice@zulip.com sender:",
"sender:alice@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;
}
};
topic_data.reset();
suggestions = search.get_suggestions_legacy('te');
expected = [
"te",
];
assert.deepEqual(suggestions.strings, expected);
topic_data.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']) {
topic_data.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 = search.get_suggestions_legacy('te');
expected = [
"te",
"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 = search.get_suggestions_legacy('topic:staplers stream:office');
expected = [
'topic:staplers stream:office',
'topic:staplers',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions_legacy('stream:devel topic:');
expected = [
'stream:devel topic:',
'stream:devel topic:REXX',
'stream:devel',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions_legacy('stream:devel -topic:');
expected = [
'stream:devel -topic:',
'stream:devel -topic:REXX',
'stream:devel',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions_legacy('-topic:te');
expected = [
'-topic:te',
'stream:office -topic:team',
'stream:office -topic:test',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions_legacy('is:alerted stream:devel is:starred topic:');
expected = [
'is:alerted stream:devel is:starred topic:',
'is:alerted stream:devel is:starred topic:REXX',
'is:alerted stream:devel is:starred',
'is:alerted stream:devel',
'is:alerted',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions_legacy('is:private stream:devel topic:');
expected = [
'is:private stream:devel topic:',
'is:private stream:devel',
'is:private',
];
assert.deepEqual(suggestions.strings, expected);
suggestions = search.get_suggestions_legacy('topic:REXX stream:devel topic:');
expected = [
'topic:REXX stream:devel topic:',
'topic:REXX stream:devel',
'topic:REXX',
];
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;
};
topic_data.reset();
const suggestions = search.get_suggestions_legacy(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;
};
topic_data.reset();
let query = 'stream:of';
let suggestions = search.get_suggestions_legacy(query);
let expected = [
"stream:of",
"stream:office",
];
assert.deepEqual(suggestions.strings, expected);
query = '-stream:of';
suggestions = search.get_suggestions_legacy(query);
expected = [
"-stream:of",
"-stream:office",
];
assert.deepEqual(suggestions.strings, expected);
query = 'hel';
suggestions = search.get_suggestions_legacy(query);
expected = [
"hel",
"stream:dev+help",
];
assert.deepEqual(suggestions.strings, expected);
});
run_test('people_suggestions', () => {
let query = 'te';
global.stream_data.subscribed_streams = function () {
return [];
};
global.narrow_state.stream = function () {
return;
};
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',
};
const alice = {
email: 'alice@zulip.com',
user_id: 203,
full_name: 'Alice Ignore',
};
people.add(ted);
people.add(bob);
people.add(alice);
topic_data.reset();
let suggestions = search.get_suggestions_legacy(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);
function describe(q) {
return 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 = search.get_suggestions_legacy('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';
expected = [
'sender:ted+sm',
'sender:ted@zulip.com',
];
suggestions = search.get_suggestions_legacy(query);
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted@zulip.com new';
expected = [
'sender:ted@zulip.com new',
'sender:ted@zulip.com',
];
suggestions = search.get_suggestions_legacy(query);
assert.deepEqual(suggestions.strings, expected);
query = 'sender:ted@tulip.com new';
expected = [
'sender:ted@tulip.com+new',
];
suggestions = search.get_suggestions_legacy(query);
assert.deepEqual(suggestions.strings, expected);
});
run_test('operator_suggestions', () => {
// Completed operator should return nothing
let query = 'stream:';
let suggestions = search.get_suggestions_legacy(query);
let expected = [
'stream:',
];
assert.deepEqual(suggestions.strings, expected);
query = 'st';
suggestions = search.get_suggestions_legacy(query);
expected = [
'st',
'streams:public',
'is:starred',
'stream:',
];
assert.deepEqual(suggestions.strings, expected);
query = 'group-';
suggestions = search.get_suggestions_legacy(query);
expected = [
'group-',
'group-pm-with:',
];
assert.deepEqual(suggestions.strings, expected);
query = '-s';
suggestions = search.get_suggestions_legacy(query);
expected = [
'-s',
'-streams:public',
'-sender:bob@zulip.com',
'-stream:',
'-sender:',
];
assert.deepEqual(suggestions.strings, expected);
query = 'stream:Denmark is:alerted -f';
suggestions = search.get_suggestions_legacy(query);
expected = [
'stream:Denmark is:alerted -f',
'stream:Denmark is:alerted -from:bob@zulip.com',
'stream:Denmark is:alerted -from:',
'stream:Denmark is:alerted',
'stream:Denmark',
];
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;
};
topic_data.reset();
// test allowing spaces with quotes surrounding operand
let query = 'stream:"dev he"';
let suggestions = search.get_suggestions_legacy(query);
let expected = [
"stream:dev+he",
"stream:dev+help",
];
assert.deepEqual(suggestions.strings, expected);
// test mismatched quote
query = 'stream:"dev h';
suggestions = search.get_suggestions_legacy(query);
expected = [
"stream:dev+h",
"stream:dev+help",
];
assert.deepEqual(suggestions.strings, expected);
// test extra space after operator still works
query = 'stream: offi';
suggestions = search.get_suggestions_legacy(query);
expected = [
"stream:offi",
"stream:office",
];
assert.deepEqual(suggestions.strings, expected);
});