2020-08-01 03:43:15 +02:00
|
|
|
"use strict";
|
|
|
|
|
2020-10-07 10:48:54 +02:00
|
|
|
const path = require("path");
|
|
|
|
|
2020-07-25 02:02:35 +02:00
|
|
|
const _ = require("lodash");
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const requires = [];
|
2019-07-25 09:13:22 +02:00
|
|
|
const new_globals = new Set();
|
|
|
|
let old_globals = {};
|
2016-07-30 17:00:12 +02:00
|
|
|
|
|
|
|
exports.set_global = function (name, val) {
|
2019-07-25 09:13:22 +02:00
|
|
|
if (!(name in old_globals)) {
|
|
|
|
if (!(name in global)) {
|
|
|
|
new_globals.add(name);
|
|
|
|
}
|
|
|
|
old_globals[name] = global[name];
|
|
|
|
}
|
2016-07-30 17:00:12 +02:00
|
|
|
global[name] = val;
|
|
|
|
return val;
|
|
|
|
};
|
|
|
|
|
node_tests: Don't remove require cache of module in zrequire.
There is good reason to do this (explanation is bit long!). With the
TypeScript migration, and the require and ES6 migrations that come
with it, we use require instead of set_global which loads the entire
module. Suppose we have a util module, which is used by some other
module, say message_store, and util is being required in message_store
since it is removed from window. Then, if a test zrequires
message_store first, and then zrequires the util module qand mocks one
of its methods, it will not be mocked for the message_store
module. The reason is:
1. zrequire('message_store') leads to require('util').
2. zrequire('util') removes the util module from cache and it is
reloaded. Now the util module in message_store and the one in
the test will be different and any updates to it in tests won't
be reflected in the actual code.
Which can lead to confusion for folks writing tests. I'll mention this
can be avoided doing zrequire('util') first but...that is not ideal.
And, since there was one outlier test that relied on this behavior,
we add the namespace.reset_module function.
2020-08-19 17:35:27 +02:00
|
|
|
function require_path(name, fn) {
|
2017-08-09 18:26:03 +02:00
|
|
|
if (fn === undefined) {
|
2020-07-15 01:29:15 +02:00
|
|
|
fn = "../../static/js/" + name;
|
2019-10-04 23:08:11 +02:00
|
|
|
} else if (/^generated\/|^js\/|^shared\/|^third\//.test(fn)) {
|
2019-06-21 01:59:21 +02:00
|
|
|
// FIXME: Stealing part of the NPM namespace is confusing.
|
2020-07-15 01:29:15 +02:00
|
|
|
fn = "../../static/" + fn;
|
2017-08-09 18:26:03 +02:00
|
|
|
}
|
node_tests: Don't remove require cache of module in zrequire.
There is good reason to do this (explanation is bit long!). With the
TypeScript migration, and the require and ES6 migrations that come
with it, we use require instead of set_global which loads the entire
module. Suppose we have a util module, which is used by some other
module, say message_store, and util is being required in message_store
since it is removed from window. Then, if a test zrequires
message_store first, and then zrequires the util module qand mocks one
of its methods, it will not be mocked for the message_store
module. The reason is:
1. zrequire('message_store') leads to require('util').
2. zrequire('util') removes the util module from cache and it is
reloaded. Now the util module in message_store and the one in
the test will be different and any updates to it in tests won't
be reflected in the actual code.
Which can lead to confusion for folks writing tests. I'll mention this
can be avoided doing zrequire('util') first but...that is not ideal.
And, since there was one outlier test that relied on this behavior,
we add the namespace.reset_module function.
2020-08-19 17:35:27 +02:00
|
|
|
|
|
|
|
return fn;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.zrequire = function (name, fn) {
|
|
|
|
fn = require_path(name, fn);
|
2019-03-24 08:37:53 +01:00
|
|
|
requires.push(fn);
|
2019-07-26 03:47:36 +02:00
|
|
|
return require(fn);
|
2017-08-09 18:26:03 +02:00
|
|
|
};
|
|
|
|
|
node_tests: Don't remove require cache of module in zrequire.
There is good reason to do this (explanation is bit long!). With the
TypeScript migration, and the require and ES6 migrations that come
with it, we use require instead of set_global which loads the entire
module. Suppose we have a util module, which is used by some other
module, say message_store, and util is being required in message_store
since it is removed from window. Then, if a test zrequires
message_store first, and then zrequires the util module qand mocks one
of its methods, it will not be mocked for the message_store
module. The reason is:
1. zrequire('message_store') leads to require('util').
2. zrequire('util') removes the util module from cache and it is
reloaded. Now the util module in message_store and the one in
the test will be different and any updates to it in tests won't
be reflected in the actual code.
Which can lead to confusion for folks writing tests. I'll mention this
can be avoided doing zrequire('util') first but...that is not ideal.
And, since there was one outlier test that relied on this behavior,
we add the namespace.reset_module function.
2020-08-19 17:35:27 +02:00
|
|
|
exports.reset_module = function (name, fn) {
|
|
|
|
fn = require_path(name, fn);
|
|
|
|
delete require.cache[require.resolve(fn)];
|
|
|
|
return require(fn);
|
|
|
|
};
|
|
|
|
|
2020-02-27 15:40:59 +01:00
|
|
|
exports.clear_zulip_refs = function () {
|
|
|
|
/*
|
|
|
|
This is a big hammer to make sure
|
|
|
|
we are not "borrowing" a transitively
|
|
|
|
required module from a previous test.
|
|
|
|
This kind of leak can make it seems
|
|
|
|
like we've written the second test
|
|
|
|
correctly, but it will fail if we
|
|
|
|
run it standalone.
|
|
|
|
*/
|
2020-10-07 10:48:54 +02:00
|
|
|
const staticPath = path.resolve(__dirname, "../../static") + path.sep;
|
2020-02-27 15:40:59 +01:00
|
|
|
_.each(require.cache, (_, fn) => {
|
2020-10-07 10:48:54 +02:00
|
|
|
if (fn.startsWith(staticPath) && !fn.startsWith(staticPath + "templates" + path.sep)) {
|
|
|
|
delete require.cache[fn];
|
2020-02-27 15:40:59 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-07-30 17:00:12 +02:00
|
|
|
exports.restore = function () {
|
2021-01-22 22:29:08 +01:00
|
|
|
for (const fn of requires) {
|
2016-07-30 19:34:51 +02:00
|
|
|
delete require.cache[require.resolve(fn)];
|
2021-01-22 22:29:08 +01:00
|
|
|
}
|
2020-02-09 04:15:38 +01:00
|
|
|
Object.assign(global, old_globals);
|
2019-07-25 09:13:22 +02:00
|
|
|
old_globals = {};
|
|
|
|
for (const name of new_globals) {
|
|
|
|
delete global[name];
|
|
|
|
}
|
|
|
|
new_globals.clear();
|
2016-07-30 17:00:12 +02:00
|
|
|
};
|
|
|
|
|
2016-07-30 20:01:15 +02:00
|
|
|
exports.stub_out_jquery = function () {
|
2021-02-10 04:53:22 +01:00
|
|
|
const $ = exports.set_global("$", () => ({
|
2020-07-20 22:18:43 +02:00
|
|
|
on() {},
|
|
|
|
trigger() {},
|
|
|
|
hide() {},
|
|
|
|
removeClass() {},
|
2020-07-02 01:45:54 +02:00
|
|
|
}));
|
2016-07-30 20:01:15 +02:00
|
|
|
$.fn = {};
|
|
|
|
$.now = function () {};
|
|
|
|
};
|
|
|
|
|
2020-07-27 16:06:46 +02:00
|
|
|
exports.with_field = function (obj, field, val, f) {
|
|
|
|
const old_val = obj[field];
|
|
|
|
obj[field] = val;
|
|
|
|
f();
|
|
|
|
obj[field] = old_val;
|
|
|
|
};
|
|
|
|
|
2017-03-11 21:07:24 +01:00
|
|
|
exports.with_overrides = function (test_function) {
|
|
|
|
// This function calls test_function() and passes in
|
|
|
|
// a way to override the namespace temporarily.
|
|
|
|
|
2020-07-26 13:21:15 +02:00
|
|
|
const restore_callbacks = [];
|
2020-07-26 13:32:02 +02:00
|
|
|
const unused_funcs = new Map();
|
2017-03-11 21:07:24 +01:00
|
|
|
|
2021-02-11 01:23:23 +01:00
|
|
|
const override = function (module, func_name, f) {
|
2020-07-26 14:31:16 +02:00
|
|
|
if (typeof f !== "function") {
|
2020-10-07 11:51:57 +02:00
|
|
|
throw new TypeError("You can only override with a function.");
|
2020-07-26 14:31:16 +02:00
|
|
|
}
|
|
|
|
|
2021-02-11 01:23:23 +01:00
|
|
|
if (!unused_funcs.has(module)) {
|
|
|
|
unused_funcs.set(module, new Map());
|
2017-03-12 14:32:12 +01:00
|
|
|
}
|
2021-02-11 01:23:23 +01:00
|
|
|
unused_funcs.get(module).set(func_name, true);
|
2017-03-12 14:32:12 +01:00
|
|
|
|
2021-02-11 01:23:23 +01:00
|
|
|
const old_f = module[func_name];
|
|
|
|
module[func_name] = function (...args) {
|
|
|
|
unused_funcs.get(module).delete(func_name);
|
2020-07-26 13:32:02 +02:00
|
|
|
return f.apply(this, args);
|
|
|
|
};
|
2017-03-11 21:07:24 +01:00
|
|
|
|
2020-07-26 13:21:15 +02:00
|
|
|
restore_callbacks.push(() => {
|
2021-02-11 01:23:23 +01:00
|
|
|
module[func_name] = old_f;
|
2017-03-11 21:07:24 +01:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
test_function(override);
|
|
|
|
|
2020-07-26 17:24:59 +02:00
|
|
|
restore_callbacks.reverse();
|
2020-07-26 13:21:15 +02:00
|
|
|
for (const restore_callback of restore_callbacks) {
|
|
|
|
restore_callback();
|
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
|
|
|
}
|
2020-07-26 13:32:02 +02:00
|
|
|
|
2021-02-11 01:23:23 +01:00
|
|
|
for (const module_unused_funcs of unused_funcs.values()) {
|
|
|
|
for (const unused_name of module_unused_funcs.keys()) {
|
|
|
|
throw new Error(unused_name + " never got invoked!");
|
|
|
|
}
|
2020-07-26 13:32:02 +02:00
|
|
|
}
|
2017-03-11 21:07:24 +01:00
|
|
|
};
|