Files
zulip/frontend_tests/zjsunit/namespace.js
Steve Howell 0d68a23066 zjsunit: Handle duplicate overrides.
We want to undo overrides in reverse order,
which is important if you override the
same name more than once in the same
function.

Until today the code basically prevented
us from ever using the original implementation
of a name we stubbed, and most of them start
as undefined due to their parent modules
starting with `set_global`.

But I do want this proper, and I introduced
a tiny pitfall today.
2020-07-26 12:50:07 -04:00

114 lines
3.0 KiB
JavaScript

const requires = [];
const new_globals = new Set();
let old_globals = {};
exports.set_global = function (name, val) {
if (!(name in old_globals)) {
if (!(name in global)) {
new_globals.add(name);
}
old_globals[name] = global[name];
}
global[name] = val;
return val;
};
exports.zrequire = function (name, fn) {
if (fn === undefined) {
fn = "../../static/js/" + name;
} else if (/^generated\/|^js\/|^shared\/|^third\//.test(fn)) {
// FIXME: Stealing part of the NPM namespace is confusing.
fn = "../../static/" + fn;
}
delete require.cache[require.resolve(fn)];
requires.push(fn);
return require(fn);
};
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.
*/
_.each(require.cache, (_, fn) => {
if (fn.indexOf("static/") >= 0) {
if (fn.indexOf("static/templates") < 0) {
delete require.cache[fn];
}
}
});
};
exports.restore = function () {
requires.forEach((fn) => {
delete require.cache[require.resolve(fn)];
});
Object.assign(global, old_globals);
old_globals = {};
for (const name of new_globals) {
delete global[name];
}
new_globals.clear();
};
exports.stub_out_jquery = function () {
set_global("$", () => ({
on() {},
trigger() {},
hide() {},
removeClass() {},
}));
$.fn = {};
$.now = function () {};
};
exports.with_overrides = function (test_function) {
// This function calls test_function() and passes in
// a way to override the namespace temporarily.
const restore_callbacks = [];
const unused_funcs = new Map();
const override = function (name, f) {
if (typeof f !== "function") {
throw new Error("You can only override with a function.");
}
unused_funcs.set(name, true);
const parts = name.split(".");
const module = parts[0];
const func_name = parts[1];
if (!Object.prototype.hasOwnProperty.call(global, module)) {
throw new Error("you must first use set_global/zrequire for " + module);
}
const old_f = global[module][func_name];
global[module][func_name] = function (...args) {
unused_funcs.delete(name);
return f.apply(this, args);
};
restore_callbacks.push(() => {
global[module][func_name] = old_f;
});
};
test_function(override);
restore_callbacks.reverse();
for (const restore_callback of restore_callbacks) {
restore_callback();
}
for (const unused_name of unused_funcs.keys()) {
throw new Error(unused_name + " never got invoked!");
}
};