mirror of https://github.com/zulip/zulip.git
259 lines
7.9 KiB
JavaScript
259 lines
7.9 KiB
JavaScript
"use strict";
|
|
|
|
const {strict: assert} = require("assert");
|
|
|
|
/*
|
|
When using zjquery, the first call to $("#foo")
|
|
returns a new instance of the FakeElement pseudoclass,
|
|
and then subsequent calls to $("#foo") get the
|
|
same instance.
|
|
*/
|
|
const FakeElement = require("./zjquery_element");
|
|
const FakeEvent = require("./zjquery_event");
|
|
|
|
function verify_selector_for_zulip(selector) {
|
|
const is_valid =
|
|
"<#.".includes(selector[0]) ||
|
|
selector === "window-stub" ||
|
|
selector === "document-stub" ||
|
|
selector === "body" ||
|
|
selector === "html" ||
|
|
selector.location ||
|
|
selector.includes("#") ||
|
|
selector.includes(".") ||
|
|
(selector.includes("[") && selector.indexOf("]") >= selector.indexOf("["));
|
|
|
|
assert.ok(
|
|
is_valid,
|
|
// Check if selector has only english alphabets and space.
|
|
// Then, the user is probably trying to use a tag as a selector
|
|
// like $('div a').
|
|
/^[ A-Za-z]+$/.test(selector)
|
|
? "Selector too broad! Use id, class or attributes of target instead."
|
|
: `Invalid selector: ${selector}. Use $.create() maybe?`,
|
|
);
|
|
}
|
|
|
|
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) {
|
|
const $elem = FakeElement(selector, {...create_opts});
|
|
Object.assign($elem, fn);
|
|
|
|
// Create a proxy handler to detect missing stubs.
|
|
//
|
|
// For context, zjquery doesn't implement every method/attribute
|
|
// that you'd find on a "real" jQuery object. Sometimes we
|
|
// expects devs to create their own stubs.
|
|
const handler = {
|
|
get(target, key) {
|
|
// Handle the special case of equality checks, which
|
|
// we can infer by assert.equal trying to access the
|
|
// "stack" key.
|
|
assert.notEqual(
|
|
key,
|
|
"stack",
|
|
"\nInstead of doing equality checks on a full object, " +
|
|
'do `assert.equal($foo.selector, ".some_class")\n',
|
|
);
|
|
|
|
const val = target[key];
|
|
|
|
/* istanbul ignore if */
|
|
if (val === undefined && typeof key !== "symbol" && key !== "inspect") {
|
|
// For undefined values, we'll throw errors to devs saying
|
|
// they need to create stubs. We ignore certain keys that
|
|
// are used for simply printing out the object.
|
|
throw new Error('You must create a stub for $("' + selector + '").' + key);
|
|
}
|
|
|
|
return val;
|
|
},
|
|
};
|
|
|
|
const proxy = new Proxy($elem, handler);
|
|
|
|
return proxy;
|
|
}
|
|
|
|
let initialize_function;
|
|
|
|
const zjquery = function (arg, arg2) {
|
|
if (typeof arg === "function") {
|
|
assert.ok(
|
|
!initialize_function,
|
|
`
|
|
We are trying to avoid the $(...) mechanism
|
|
for initializing modules in our codebase,
|
|
and the code that you are compiling/running
|
|
has tried to do this twice. Please either
|
|
clean up the real code or reduce the scope
|
|
of what you are testing in this test module.
|
|
`,
|
|
);
|
|
initialize_function = arg;
|
|
return undefined;
|
|
}
|
|
|
|
// If somebody is passing us an element, we return
|
|
// the element itself if it's been created with
|
|
// zjquery.
|
|
// This may happen in cases like $(this).
|
|
if (arg.selector && elems.has(arg.selector)) {
|
|
return arg;
|
|
}
|
|
|
|
// We occasionally create stub objects that know
|
|
// they want to be wrapped by jQuery (so they can
|
|
// in turn return stubs). The convention is that
|
|
// they provide a to_$ attribute.
|
|
if (arg.to_$) {
|
|
assert.equal(typeof arg.to_$, "function");
|
|
return arg.to_$();
|
|
}
|
|
|
|
assert.equal(
|
|
arg2,
|
|
undefined,
|
|
"We only use one-argument variations of $(...) in Zulip code.",
|
|
);
|
|
|
|
const selector = arg;
|
|
|
|
/* istanbul ignore if */
|
|
if (typeof selector !== "string") {
|
|
console.info(arg);
|
|
throw new Error("zjquery does not know how to wrap this object yet");
|
|
}
|
|
|
|
verify_selector_for_zulip(selector);
|
|
|
|
if (!elems.has(selector)) {
|
|
const $elem = new_elem(selector);
|
|
elems.set(selector, $elem);
|
|
}
|
|
return elems.get(selector);
|
|
};
|
|
|
|
zjquery.get_initialize_function = function () {
|
|
return initialize_function;
|
|
};
|
|
|
|
zjquery.clear_initialize_function = function () {
|
|
initialize_function = undefined;
|
|
};
|
|
|
|
zjquery.create = function (name, opts) {
|
|
assert.ok(!elems.has(name), "You already created an object with this name!!");
|
|
const $elem = new_elem(name, opts);
|
|
elems.set(name, $elem);
|
|
|
|
return $elem;
|
|
};
|
|
|
|
/* istanbul ignore next */
|
|
zjquery.state = function () {
|
|
// useful for debugging
|
|
let res = [...elems.values()].map(($v) => $v.debug());
|
|
|
|
res = res.map((v) => [v.selector, v.value, v.shown]);
|
|
|
|
res.sort();
|
|
|
|
return res;
|
|
};
|
|
|
|
zjquery.Event = FakeEvent;
|
|
|
|
/* istanbul ignore next */
|
|
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;
|
|
}
|
|
|
|
/* istanbul ignore next */
|
|
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();
|
|
};
|
|
|
|
zjquery.validator = {
|
|
/* istanbul ignore next */
|
|
addMethod() {
|
|
throw new Error("You must create your own $.validator.addMethod stub.");
|
|
},
|
|
};
|
|
|
|
return zjquery;
|
|
}
|
|
|
|
const $ = new Proxy(make_zjquery(), {
|
|
set(obj, prop, value) {
|
|
if (obj[prop] && obj[prop]._patched_with_override) {
|
|
obj[prop] = value;
|
|
return true;
|
|
}
|
|
|
|
if (value._patched_with_override) {
|
|
obj[prop] = value;
|
|
return true;
|
|
}
|
|
|
|
/* istanbul ignore next */
|
|
throw new Error(`
|
|
Please don't modify $.${prop} if you are using zjquery.
|
|
|
|
You can do this instead:
|
|
|
|
override($, "${prop}", () => {...});
|
|
|
|
Or you can do this if you don't actually
|
|
need zjquery and just want to simulate one function.
|
|
|
|
mock_cjs("jquery", {
|
|
${prop}(...) {...},
|
|
});
|
|
|
|
It's also possible that you are testing code with
|
|
node tests when it would be a better strategy to
|
|
use puppeteer tests.
|
|
`);
|
|
},
|
|
});
|
|
|
|
module.exports = $; // eslint-disable-line no-jquery/variable-pattern
|