2020-08-01 03:43:15 +02:00
|
|
|
"use strict";
|
|
|
|
|
2020-11-30 23:46:45 +01:00
|
|
|
const {strict: assert} = require("assert");
|
|
|
|
|
2021-06-12 17:50:07 +02:00
|
|
|
/*
|
|
|
|
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");
|
2020-07-21 05:46:22 +02:00
|
|
|
|
2021-02-22 14:34:22 +01:00
|
|
|
function verify_selector_for_zulip(selector) {
|
|
|
|
const is_valid =
|
|
|
|
"<#.".includes(selector[0]) ||
|
|
|
|
selector === "window-stub" ||
|
|
|
|
selector === "document-stub" ||
|
|
|
|
selector === "body" ||
|
|
|
|
selector === "html" ||
|
2024-03-28 21:00:04 +01:00
|
|
|
selector === ":root" ||
|
2021-02-22 14:34:22 +01:00
|
|
|
selector.location ||
|
|
|
|
selector.includes("#") ||
|
|
|
|
selector.includes(".") ||
|
|
|
|
(selector.includes("[") && selector.indexOf("]") >= selector.indexOf("["));
|
|
|
|
|
2022-04-09 23:44:38 +02:00
|
|
|
assert.ok(
|
|
|
|
is_valid,
|
2021-02-22 14:37:49 +01:00
|
|
|
// 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').
|
2022-04-09 23:44:38 +02:00
|
|
|
/^[ A-Za-z]+$/.test(selector)
|
|
|
|
? "Selector too broad! Use id, class or attributes of target instead."
|
|
|
|
: `Invalid selector: ${selector}. Use $.create() maybe?`,
|
|
|
|
);
|
2021-02-22 14:34:22 +01:00
|
|
|
}
|
|
|
|
|
2021-02-21 15:38:51 +01:00
|
|
|
function make_zjquery() {
|
2020-02-12 08:35:40 +01:00
|
|
|
const elems = new Map();
|
2018-04-16 20:43:55 +02:00
|
|
|
|
|
|
|
// Our fn structure helps us simulate extending jQuery.
|
2021-02-22 13:55:34 +01:00
|
|
|
// Use this with extreme caution.
|
2019-11-02 00:06:25 +01:00
|
|
|
const fn = {};
|
2017-05-25 01:49:29 +02:00
|
|
|
|
2021-02-08 15:48:50 +01:00
|
|
|
function new_elem(selector, create_opts) {
|
2022-01-25 11:36:19 +01:00
|
|
|
const $elem = FakeElement(selector, {...create_opts});
|
|
|
|
Object.assign($elem, fn);
|
2019-04-18 21:11:30 +02:00
|
|
|
|
|
|
|
// 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.
|
2019-11-02 00:06:25 +01:00
|
|
|
const handler = {
|
2022-11-17 23:33:43 +01:00
|
|
|
get(target, key) {
|
2019-04-18 21:11:30 +02:00
|
|
|
// Handle the special case of equality checks, which
|
|
|
|
// we can infer by assert.equal trying to access the
|
|
|
|
// "stack" key.
|
2022-04-09 23:44:38 +02:00
|
|
|
assert.notEqual(
|
|
|
|
key,
|
|
|
|
"stack",
|
|
|
|
"\nInstead of doing equality checks on a full object, " +
|
|
|
|
'do `assert.equal($foo.selector, ".some_class")\n',
|
|
|
|
);
|
2019-04-18 21:11:30 +02:00
|
|
|
|
|
|
|
const val = target[key];
|
|
|
|
|
2022-04-09 23:44:38 +02:00
|
|
|
/* istanbul ignore if */
|
2020-09-24 07:50:36 +02:00
|
|
|
if (val === undefined && typeof key !== "symbol" && key !== "inspect") {
|
2019-04-18 21:11:30 +02:00
|
|
|
// 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.
|
2020-10-07 09:58:04 +02:00
|
|
|
throw new Error('You must create a stub for $("' + selector + '").' + key);
|
2019-04-18 21:11:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-01-25 11:36:19 +01:00
|
|
|
const proxy = new Proxy($elem, handler);
|
2019-04-18 21:11:30 +02:00
|
|
|
|
|
|
|
return proxy;
|
2017-05-25 01:49:29 +02:00
|
|
|
}
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2021-03-13 15:49:01 +01:00
|
|
|
let initialize_function;
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const zjquery = function (arg, arg2) {
|
2017-05-25 01:49:29 +02:00
|
|
|
if (typeof arg === "function") {
|
2022-04-09 23:44:38 +02:00
|
|
|
assert.ok(
|
|
|
|
!initialize_function,
|
|
|
|
`
|
2021-03-13 15:49:01 +01:00
|
|
|
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.
|
2022-04-09 23:44:38 +02:00
|
|
|
`,
|
|
|
|
);
|
2021-03-13 15:49:01 +01:00
|
|
|
initialize_function = arg;
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-04-12 18:06:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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).
|
2020-02-12 08:35:40 +01:00
|
|
|
if (arg.selector && elems.has(arg.selector)) {
|
|
|
|
return arg;
|
2017-05-25 01:49:29 +02:00
|
|
|
}
|
2017-05-24 03:34:18 +02:00
|
|
|
|
2018-04-12 18:06:39 +02:00
|
|
|
// 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_$) {
|
2021-06-08 05:08:12 +02:00
|
|
|
assert.equal(typeof arg.to_$, "function");
|
2018-04-12 18:06:39 +02:00
|
|
|
return arg.to_$();
|
|
|
|
}
|
|
|
|
|
2022-04-09 23:44:38 +02:00
|
|
|
assert.equal(
|
|
|
|
arg2,
|
|
|
|
undefined,
|
|
|
|
"We only use one-argument variations of $(...) in Zulip code.",
|
|
|
|
);
|
2018-04-05 16:46:45 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const selector = arg;
|
2017-07-08 15:16:19 +02:00
|
|
|
|
2022-04-09 23:44:38 +02:00
|
|
|
/* istanbul ignore if */
|
2018-04-12 18:06:39 +02:00
|
|
|
if (typeof selector !== "string") {
|
|
|
|
console.info(arg);
|
2020-10-07 09:58:04 +02:00
|
|
|
throw new Error("zjquery does not know how to wrap this object yet");
|
2018-04-12 18:06:39 +02:00
|
|
|
}
|
|
|
|
|
2021-02-22 14:34:22 +01:00
|
|
|
verify_selector_for_zulip(selector);
|
2017-07-08 15:16:19 +02:00
|
|
|
|
2020-02-12 08:35:40 +01:00
|
|
|
if (!elems.has(selector)) {
|
2022-01-25 11:36:19 +01:00
|
|
|
const $elem = new_elem(selector);
|
|
|
|
elems.set(selector, $elem);
|
2017-05-24 03:34:18 +02:00
|
|
|
}
|
2020-02-12 08:35:40 +01:00
|
|
|
return elems.get(selector);
|
2017-05-25 01:49:29 +02:00
|
|
|
};
|
2017-05-24 03:34:18 +02:00
|
|
|
|
2021-03-13 15:49:01 +01:00
|
|
|
zjquery.get_initialize_function = function () {
|
|
|
|
return initialize_function;
|
|
|
|
};
|
|
|
|
|
|
|
|
zjquery.clear_initialize_function = function () {
|
|
|
|
initialize_function = undefined;
|
|
|
|
};
|
|
|
|
|
2021-02-08 15:48:50 +01:00
|
|
|
zjquery.create = function (name, opts) {
|
2021-06-10 08:32:54 +02:00
|
|
|
assert.ok(!elems.has(name), "You already created an object with this name!!");
|
2022-01-25 11:36:19 +01:00
|
|
|
const $elem = new_elem(name, opts);
|
|
|
|
elems.set(name, $elem);
|
2021-02-08 15:48:50 +01:00
|
|
|
|
2022-01-25 11:36:19 +01:00
|
|
|
return $elem;
|
2017-07-08 15:16:19 +02:00
|
|
|
};
|
2017-05-24 03:05:29 +02:00
|
|
|
|
2022-04-09 23:44:38 +02:00
|
|
|
/* istanbul ignore next */
|
2017-05-25 01:49:29 +02:00
|
|
|
zjquery.state = function () {
|
|
|
|
// useful for debugging
|
2023-03-02 01:58:25 +01:00
|
|
|
let res = [...elems.values()].map(($v) => $v.debug());
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
res = res.map((v) => [v.selector, v.value, v.shown]);
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
res.sort();
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
return res;
|
|
|
|
};
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2021-06-12 17:50:07 +02:00
|
|
|
zjquery.Event = FakeEvent;
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2022-04-09 23:44:38 +02:00
|
|
|
/* istanbul ignore next */
|
2021-02-22 13:55:34 +01:00
|
|
|
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, {
|
2023-09-25 06:08:38 +02:00
|
|
|
set(_obj, _prop, _value) {
|
2022-04-09 23:44:38 +02:00
|
|
|
/* istanbul ignore next */
|
2021-02-22 13:55:34 +01:00
|
|
|
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
|
2023-12-14 23:51:33 +01:00
|
|
|
replace the wrapper with tests.lib.noop.
|
2021-02-22 13:55:34 +01:00
|
|
|
`);
|
|
|
|
},
|
|
|
|
});
|
2018-04-12 14:45:06 +02:00
|
|
|
|
2018-07-05 14:58:14 +02:00
|
|
|
zjquery.clear_all_elements = function () {
|
2020-02-12 08:35:40 +01:00
|
|
|
elems.clear();
|
2018-07-05 14:58:14 +02:00
|
|
|
};
|
|
|
|
|
2021-02-22 13:10:25 +01:00
|
|
|
zjquery.validator = {
|
2022-04-09 23:44:38 +02:00
|
|
|
/* istanbul ignore next */
|
2021-02-22 13:10:25 +01:00
|
|
|
addMethod() {
|
|
|
|
throw new Error("You must create your own $.validator.addMethod stub.");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
return zjquery;
|
2021-02-21 15:38:51 +01:00
|
|
|
}
|
|
|
|
|
2021-02-22 12:18:06 +01:00
|
|
|
const $ = new Proxy(make_zjquery(), {
|
|
|
|
set(obj, prop, value) {
|
2021-03-11 18:01:52 +01:00
|
|
|
if (obj[prop] && obj[prop]._patched_with_override) {
|
|
|
|
obj[prop] = value;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-02-22 12:18:06 +01:00
|
|
|
if (value._patched_with_override) {
|
|
|
|
obj[prop] = value;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-09 23:44:38 +02:00
|
|
|
/* istanbul ignore next */
|
2021-02-22 12:18:06 +01:00
|
|
|
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.
|
|
|
|
|
2021-04-23 20:16:42 +02:00
|
|
|
mock_cjs("jquery", {
|
2021-02-22 12:18:06 +01:00
|
|
|
${prop}(...) {...},
|
|
|
|
});
|
|
|
|
|
|
|
|
It's also possible that you are testing code with
|
|
|
|
node tests when it would be a better strategy to
|
|
|
|
use puppeteer tests.
|
|
|
|
`);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2022-01-25 11:36:19 +01:00
|
|
|
module.exports = $; // eslint-disable-line no-jquery/variable-pattern
|