mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 03:53:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			228 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| const assert = require("node:assert/strict");
 | |
| 
 | |
| /*
 | |
|     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.cjs");
 | |
| const FakeEvent = require("./zjquery_event.cjs");
 | |
| 
 | |
| function verify_selector_for_zulip(selector) {
 | |
|     const is_valid =
 | |
|         "<#.".includes(selector[0]) ||
 | |
|         selector === "window-stub" ||
 | |
|         selector === "document-stub" ||
 | |
|         selector === "body" ||
 | |
|         selector === "html" ||
 | |
|         selector === ":root" ||
 | |
|         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;
 | |
|     }
 | |
| 
 | |
|     const zjquery = function (arg, arg2) {
 | |
|         assert.ok(typeof arg !== "function", "zjquery does not support $(callback)");
 | |
| 
 | |
|         // 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.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) {
 | |
|             /* 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 tests.lib.noop.
 | |
|             `);
 | |
|         },
 | |
|     });
 | |
| 
 | |
|     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
 |