2020-02-21 00:20:39 +01:00
|
|
|
const rewiremock = require("rewiremock/node");
|
|
|
|
|
2018-12-26 19:56:19 +01:00
|
|
|
/*
|
|
|
|
This test suite is designed to find errors
|
|
|
|
in our initialization sequence. It doesn't
|
|
|
|
really validate any behavior, other than just
|
|
|
|
making sure things don't fail. For more
|
|
|
|
directed testing of individual modules, you
|
|
|
|
should create dedicated test suites.
|
|
|
|
|
|
|
|
Also, we stub a lot of initialization here that
|
|
|
|
is tricky to test due to dependencies on things
|
|
|
|
like jQuery. A good project is to work through
|
|
|
|
ignore_modules and try to make this test more
|
|
|
|
complete.
|
|
|
|
|
|
|
|
Also, it's good to be alert here for things
|
|
|
|
that can be cleaned up in the code--for example,
|
|
|
|
not everything needs to happen in `initialization`--
|
|
|
|
some things can happen later in a `launch` method.
|
|
|
|
|
|
|
|
*/
|
2020-02-13 22:34:29 +01:00
|
|
|
const util = zrequire('util');
|
2018-12-26 19:56:19 +01:00
|
|
|
set_global('document', {
|
|
|
|
location: {
|
|
|
|
protocol: 'http',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
set_global('csrf_token', 'whatever');
|
|
|
|
|
|
|
|
set_global('$', () => {});
|
|
|
|
set_global('resize', {});
|
|
|
|
set_global('page_params', {});
|
|
|
|
|
|
|
|
const ignore_modules = [
|
|
|
|
'activity',
|
|
|
|
'click_handlers',
|
|
|
|
'compose_pm_pill',
|
|
|
|
'copy_and_paste',
|
|
|
|
'drafts',
|
|
|
|
'emoji',
|
|
|
|
'emoji_picker',
|
|
|
|
'gear_menu',
|
|
|
|
'hashchange',
|
|
|
|
'hotspots',
|
2020-02-19 21:59:26 +01:00
|
|
|
// Accesses home_msg_list, which is a lot of complexity to setup
|
|
|
|
'message_fetch',
|
2018-12-26 19:56:19 +01:00
|
|
|
'message_scroll',
|
|
|
|
'message_viewport',
|
|
|
|
'panels',
|
|
|
|
'popovers',
|
|
|
|
'reload',
|
|
|
|
'scroll_bar',
|
|
|
|
'server_events',
|
|
|
|
'settings_sections',
|
|
|
|
'settings_panel_menu',
|
|
|
|
'settings_toggle',
|
|
|
|
'subs',
|
|
|
|
'timerender',
|
|
|
|
'ui',
|
|
|
|
'unread_ui',
|
|
|
|
];
|
|
|
|
|
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 mod of ignore_modules) {
|
2018-12-26 19:56:19 +01:00
|
|
|
set_global(mod, {
|
|
|
|
initialize: () => {},
|
|
|
|
});
|
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
|
|
|
}
|
2018-12-26 19:56:19 +01:00
|
|
|
|
2020-02-06 00:04:34 +01:00
|
|
|
emoji.emojis_by_name = new Map();
|
2018-12-26 19:56:19 +01:00
|
|
|
|
|
|
|
util.is_mobile = () => false;
|
2019-07-11 05:06:20 +02:00
|
|
|
global.stub_templates(() => 'some-html');
|
2020-07-02 01:39:34 +02:00
|
|
|
ui.get_scroll_element = (element) => element;
|
2018-12-26 19:56:19 +01:00
|
|
|
|
2020-02-26 23:53:46 +01:00
|
|
|
zrequire('alert_words');
|
markdown: Add helper configuration for mobile.
This refactoring is the first step toward sharing
our markdown code with mobile. This focuses on
the Zulip layer, not the underlying third party `marked`
library.
In this commit we do a one-time initialization to
wire up the markdown functions, but after further
discussions with Greg, it might make more sense
to just pass in helpers on every use of markdown
(which is generally only once per sent message).
I'll address that in follow-up commits.
Even though it looks like a pretty invasive change,
you will note that we barely needed to modify the
node tests to make this pass. And we have pretty
decent test coverage here.
All of the places where we used to depend on
other Zulip modules now use helper functions that
any client (e.g. mobile) can configure themselves.
Or course, in the webapp, we configure these from
modules like people/stream_data/hash_util/etc.
Even in places where markdown used to deal directly with
data structures from other modules, we now use functions.
We may revisit this in a future commit, and we might
just pass data directly for certain things.
I decided to keep the helpers data structure completely flat,
so we don't have ugly nested names like
`helpers.emoji.get_emoji_codepoint`. Because of this,
some of the names aren't 1:1, which I think is fine.
For example, we map `user_groups.is_member_of` to
`is_member_of_user_group`.
It's likely that mobile already has different names
for their versions of these functions, so trying for
fake consistency would only help the webapp. In some
cases, I think the webapp functions have names that
could be improved, but we can clean that up in future
commits, and since the names aren't coupled to markdown
itself (i.e. only the config), we will be less
constrained.
It's worth noting that `marked` has an `options`
data structure that it uses for configuration, but
I didn't piggyback onto it, since the `marked`
options are more at the lexing/parsing layer vs.
the app-data layer stuff that our helpers mostly
help with.
Hopefully it's obvious why I just put helpers in
the top-level namespace for the module rather than
passing it around through multiple layers of the
parser.
There were a couple places in markdown where we
were doing awkward `hasOwnProperty` checks for
emoji-related stuff. Now we use the Python
principle of ask-forgiveness-not-permission and
just handle the getters returning falsy data. (It
should be `undefined`, but any falsy value is
unworkable in the places I changed, so I use
the simpler, less brittle form.)
We also break our direct dependency on
`emoji_codes.json` (with some help from the
prior commit).
In one place I rename streamName to stream_name,
fixing up an ancient naming violation that goes
way back to before this code was even extracted
away from echo.js. I didn't bother to split this
out into a separate commit, since 2 of the 4
lines would be immediately re-modified in the
subsequent commit.
Note that we still depend on `fenced_code`
via the global namespace, instead of simply
requiring it directly or injecting it. The
reason I'm postponing any action there is that
we'll have to change things once we move
markdown into a shared library. (The most
likely outcome is that we'll rename/move both files
at the same time and fix the namespace/require
details as part of that commit.)
Also the markdown code still relies on `_` being
available in the global namespace. We aren't
quite ready to share code with mobile yet, but the
underscore dependency should not be problematic,
since mobile already uses underscore to use the
webapp's shared typing_status module.
2020-02-13 13:54:11 +01:00
|
|
|
zrequire('hash_util');
|
2018-12-26 19:56:19 +01:00
|
|
|
zrequire('echo');
|
|
|
|
zrequire('colorspace');
|
|
|
|
zrequire('stream_color');
|
|
|
|
zrequire('stream_edit');
|
|
|
|
zrequire('color_data');
|
|
|
|
zrequire('stream_data');
|
|
|
|
zrequire('muting');
|
|
|
|
zrequire('condense');
|
2020-04-04 22:14:34 +02:00
|
|
|
zrequire('spoilers');
|
2018-12-26 19:56:19 +01:00
|
|
|
zrequire('lightbox');
|
|
|
|
zrequire('overlays');
|
|
|
|
zrequire('invite');
|
|
|
|
zrequire('tab_bar');
|
|
|
|
zrequire('narrow_state');
|
|
|
|
zrequire('people');
|
2020-02-07 16:33:03 +01:00
|
|
|
zrequire('presence');
|
2018-12-26 19:56:19 +01:00
|
|
|
zrequire('search_pill_widget');
|
|
|
|
zrequire('user_groups');
|
|
|
|
zrequire('unread');
|
|
|
|
zrequire('bot_data');
|
2019-07-26 03:47:36 +02:00
|
|
|
set_global('marked', zrequire('marked', 'third/marked/lib/marked'));
|
2018-12-26 19:56:19 +01:00
|
|
|
zrequire('fenced_code');
|
|
|
|
zrequire('markdown');
|
|
|
|
zrequire('upload');
|
|
|
|
zrequire('compose');
|
|
|
|
zrequire('composebox_typeahead');
|
|
|
|
zrequire('narrow');
|
2019-12-25 16:58:11 +01:00
|
|
|
zrequire('search_suggestion');
|
2018-12-26 19:56:19 +01:00
|
|
|
zrequire('search');
|
|
|
|
zrequire('tutorial');
|
|
|
|
zrequire('notifications');
|
2019-11-21 21:01:15 +01:00
|
|
|
zrequire('pm_conversations');
|
2018-12-26 19:56:19 +01:00
|
|
|
zrequire('pm_list');
|
|
|
|
zrequire('list_cursor');
|
|
|
|
zrequire('keydown_util');
|
|
|
|
zrequire('stream_sort');
|
|
|
|
zrequire('stream_list');
|
|
|
|
zrequire('topic_list');
|
|
|
|
zrequire('topic_zoom');
|
|
|
|
zrequire('sent_messages');
|
|
|
|
zrequire('typing');
|
|
|
|
zrequire('top_left_corner');
|
|
|
|
zrequire('starred_messages');
|
|
|
|
zrequire('user_status');
|
2019-01-26 21:14:33 +01:00
|
|
|
zrequire('user_status_ui');
|
2020-02-21 00:20:39 +01:00
|
|
|
|
|
|
|
const ui_init = rewiremock.proxy(
|
|
|
|
() => zrequire("ui_init"),
|
|
|
|
{
|
|
|
|
"../../static/js/emojisets": {
|
|
|
|
initialize: () => {},
|
|
|
|
},
|
2020-07-02 02:16:03 +02:00
|
|
|
},
|
2020-02-21 00:20:39 +01:00
|
|
|
);
|
2019-07-26 03:47:36 +02:00
|
|
|
|
2018-12-26 19:56:19 +01:00
|
|
|
set_global('$', global.make_zjquery());
|
|
|
|
|
|
|
|
const document_stub = $.create('document-stub');
|
|
|
|
document.to_$ = () => document_stub;
|
|
|
|
document_stub.on = () => {};
|
|
|
|
document_stub.idle = () => {};
|
|
|
|
|
|
|
|
const window_stub = $.create('window-stub');
|
2019-07-25 09:13:22 +02:00
|
|
|
set_global('to_$', () => window_stub);
|
2018-12-26 19:56:19 +01:00
|
|
|
window_stub.idle = () => {};
|
|
|
|
|
|
|
|
ui_init.initialize_kitchen_sink_stuff = () => {};
|
|
|
|
|
|
|
|
page_params.realm_default_streams = [];
|
|
|
|
page_params.subscriptions = [];
|
|
|
|
page_params.unsubscribed = [];
|
|
|
|
page_params.never_subscribed = [];
|
|
|
|
page_params.realm_notifications_stream_id = -1;
|
2020-02-05 23:38:27 +01:00
|
|
|
page_params.unread_msgs = {
|
|
|
|
huddles: [],
|
|
|
|
pms: [],
|
|
|
|
streams: [],
|
|
|
|
mentions: [],
|
|
|
|
};
|
2019-11-21 21:01:15 +01:00
|
|
|
page_params.recent_private_conversations = [];
|
2020-02-06 04:31:32 +01:00
|
|
|
page_params.user_status = {};
|
2020-02-05 23:38:27 +01:00
|
|
|
page_params.realm_users = [];
|
|
|
|
page_params.realm_non_active_users = [];
|
|
|
|
page_params.cross_realm_bots = [];
|
|
|
|
page_params.muted_topics = [];
|
|
|
|
page_params.realm_user_groups = [];
|
|
|
|
page_params.realm_bots = [];
|
|
|
|
page_params.realm_filters = [];
|
|
|
|
page_params.starred_messages = [];
|
2020-02-07 16:33:03 +01:00
|
|
|
page_params.presences = [];
|
2018-12-26 19:56:19 +01:00
|
|
|
|
2020-05-21 00:53:14 +02:00
|
|
|
const $tab_bar = $.create('#tab_bar');
|
|
|
|
$tab_bar.append = () => {};
|
2019-11-21 05:24:55 +01:00
|
|
|
upload.setup_upload = () => {};
|
2018-12-26 19:56:19 +01:00
|
|
|
|
|
|
|
server_events.home_view_loaded = () => true;
|
|
|
|
|
|
|
|
resize.watch_manual_resize = () => {};
|
|
|
|
|
|
|
|
$("#stream_message_recipient_stream").typeahead = () => {};
|
|
|
|
$("#stream_message_recipient_topic").typeahead = () => {};
|
|
|
|
$("#private_message_recipient").typeahead = () => {};
|
|
|
|
$("#compose-textarea").typeahead = () => {};
|
|
|
|
$("#search_query").typeahead = () => {};
|
|
|
|
|
|
|
|
const value_stub = $.create('value');
|
|
|
|
const count_stub = $.create('count');
|
|
|
|
count_stub.set_find_results('.value', value_stub);
|
2019-01-30 18:24:56 +01:00
|
|
|
$(".top_left_starred_messages").set_find_results('.count', count_stub);
|
2018-12-26 19:56:19 +01:00
|
|
|
|
2019-04-18 21:11:30 +02:00
|
|
|
$("#tab_list .stream").length = 0;
|
2018-12-26 19:56:19 +01:00
|
|
|
|
2020-05-21 00:53:14 +02:00
|
|
|
// set find results doesn't work here since we call .empty() in the code.
|
|
|
|
$tab_bar.find = () => false;
|
|
|
|
|
2020-04-08 00:23:15 +02:00
|
|
|
compose.compute_show_video_chat_button = () => {};
|
|
|
|
$("#below-compose-content .video_link").toggle = () => {};
|
|
|
|
|
2020-04-22 19:24:52 +02:00
|
|
|
$(".narrow_description > a").hover = () => {};
|
|
|
|
|
2018-12-26 19:56:19 +01:00
|
|
|
run_test('initialize_everything', () => {
|
|
|
|
ui_init.initialize_everything();
|
|
|
|
});
|