zjquery: Prohibit extensions to $.fn.

We make an exception for the popovers code.

Luckily it's pretty rare that we extend $.fn
in our real code.
This commit is contained in:
Steve Howell 2021-02-22 12:55:34 +00:00 committed by Steve Howell
parent 89ab76920f
commit 132e67cb28
4 changed files with 49 additions and 30 deletions

View File

@ -5,7 +5,7 @@ const {strict: assert} = require("assert");
const rewiremock = require("rewiremock/node");
const {stub_templates} = require("../zjsunit/handlebars");
const {set_global, zrequire} = require("../zjsunit/namespace");
const {set_global, with_field, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const $ = require("../zjsunit/zjquery");
@ -19,7 +19,6 @@ const user_status = zrequire("user_status");
const message_edit = zrequire("message_edit");
const noop = function () {};
$.fn.popover = noop; // this will get wrapped by our code
set_global("current_msg_list", {});
set_global("page_params", {
@ -49,9 +48,14 @@ const stream_data = set_global("stream_data", {});
const ClipboardJS = noop;
const popovers = rewiremock.proxy(() => zrequire("popovers"), {
clipboard: ClipboardJS,
});
function import_popovers() {
return rewiremock.proxy(() => zrequire("popovers"), {
clipboard: ClipboardJS,
});
}
// Bypass some scary code that runs when we import the module.
const popovers = with_field($.fn, "popover", noop, import_popovers);
const alice = {
email: "alice@example.com",
@ -114,6 +118,7 @@ function test_ui(label, f) {
test_ui("sender_hover", (override) => {
override(popovers, "hide_user_profile", noop);
override($.fn, "popover", noop);
const selection = ".sender_name, .sender_name-in-status, .inline_profile_picture";
const handler = $("#main_div").get_on_handler("click", selection);
@ -208,6 +213,8 @@ test_ui("sender_hover", (override) => {
});
test_ui("actions_popover", (override) => {
override($.fn, "popover", noop);
const target = $.create("click target");
override(popovers, "hide_user_profile", noop);

View File

@ -197,26 +197,3 @@ run_test("create", () => {
obj2.addClass(".striped");
assert(obj2.hasClass(".striped"));
});
run_test("extensions", () => {
// You can extend $.fn so that all subsequent objects
// we create get a new function.
$.fn.area = function () {
return this.width() * this.height();
};
// Before we use area, though, let's illustrate that
// the predominant Zulip testing style is to stub objects
// using direct syntax:
const rect = $.create("rectangle");
rect.width = () => 5;
rect.height = () => 7;
assert.equal(rect.width(), 5);
assert.equal(rect.height(), 7);
// But we also have area available from general extension.
assert.equal(rect.area(), 35);
});

View File

@ -75,8 +75,9 @@ exports.restore = function () {
exports.with_field = function (obj, field, val, f) {
const old_val = obj[field];
obj[field] = val;
f();
const result = f();
obj[field] = old_val;
return result;
};
exports.with_overrides = function (test_function) {

View File

@ -408,6 +408,7 @@ function make_zjquery() {
const elems = new Map();
// Our fn structure helps us simulate extending jQuery.
// Use this with extreme caution.
const fn = {};
function new_elem(selector, create_opts) {
@ -543,7 +544,40 @@ function make_zjquery() {
return s;
};
zjquery.fn = fn;
fn.popover = () => {
throw new Error(`
Do not try to test $.fn.popover code unless
you really know what you are doing.
`);
};
zjquery.fn = new Proxy(fn, {
set(obj, prop, value) {
if (prop === "popover") {
// We allow our popovers test to modify
// $.fn so we can bypass a gruesome hack
// in our popovers.js module.
obj[prop] = value;
return true;
}
throw new Error(`
Please don't use node tests to test code
that extends $.fn unless you really know
what you are doing.
It's likely that you are better off testing
end-to-end behavior with puppeteer tests.
If you are trying to get coverage on a module
that extends $.fn, and you just want to skip
over that aspect of the module for the purpose
of testing, see if you can wrap the code
that extends $.fn and use override() to
replace the wrapper with () => {}.
`);
},
});
zjquery.clear_all_elements = function () {
elems.clear();