mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 03:53:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| const {strict: assert} = require("assert");
 | |
| 
 | |
| const FakeEvent = require("./zjquery_event");
 | |
| 
 | |
| const noop = function () {};
 | |
| 
 | |
| // TODO: convert this to a true class
 | |
| function FakeElement(selector, opts) {
 | |
|     let html = "never-been-set";
 | |
|     let text = "never-been-set";
 | |
|     let value;
 | |
|     let shown = false;
 | |
|     let height;
 | |
| 
 | |
|     const find_results = new Map();
 | |
|     let my_parent;
 | |
|     const parents_result = new Map();
 | |
|     const properties = new Map();
 | |
|     const attrs = new Map();
 | |
|     const classes = new Map();
 | |
|     const event_store = make_event_store(selector);
 | |
| 
 | |
|     const self = {
 | |
|         addClass(class_name) {
 | |
|             classes.set(class_name, true);
 | |
|             return self;
 | |
|         },
 | |
|         append(arg) {
 | |
|             html = html + arg;
 | |
|             return self;
 | |
|         },
 | |
|         attr(name, val) {
 | |
|             if (val === undefined) {
 | |
|                 return attrs.get(name);
 | |
|             }
 | |
|             attrs.set(name, val);
 | |
|             return self;
 | |
|         },
 | |
|         data(name, val) {
 | |
|             if (val === undefined) {
 | |
|                 return attrs.get("data-" + name);
 | |
|             }
 | |
|             attrs.set("data-" + name, val);
 | |
|             return self;
 | |
|         },
 | |
|         delay() {
 | |
|             return self;
 | |
|         },
 | |
|         debug() {
 | |
|             return {
 | |
|                 value,
 | |
|                 shown,
 | |
|                 selector,
 | |
|             };
 | |
|         },
 | |
|         empty(arg) {
 | |
|             if (arg === undefined) {
 | |
|                 find_results.clear();
 | |
|             }
 | |
|             return self;
 | |
|         },
 | |
|         eq() {
 | |
|             return self;
 | |
|         },
 | |
|         expectOne() {
 | |
|             // silently do nothing
 | |
|             return self;
 | |
|         },
 | |
|         fadeTo: noop,
 | |
|         find(child_selector) {
 | |
|             const child = find_results.get(child_selector);
 | |
|             if (child) {
 | |
|                 return child;
 | |
|             }
 | |
|             if (child === false) {
 | |
|                 // This is deliberately set to simulate missing find results.
 | |
|                 // Return an empty array, the most common check is
 | |
|                 // if ($.find().length) { //success }
 | |
|                 return [];
 | |
|             }
 | |
|             throw new Error(`
 | |
|                 We need you to simulate the results of $(...).find(...)
 | |
|                 by using set_find_results. You want something like this:
 | |
| 
 | |
|                     const container = ...;
 | |
|                     const child = ...;
 | |
|                     container.set_find_results("${child_selector}", child);
 | |
| 
 | |
|                 Then calling container.find("${child_selector}") will return
 | |
|                 the "child" zjquery element.
 | |
| 
 | |
|                 `);
 | |
|         },
 | |
|         get_on_handler(name, child_selector) {
 | |
|             return event_store.get_on_handler(name, child_selector);
 | |
|         },
 | |
|         hasClass(class_name) {
 | |
|             return classes.has(class_name);
 | |
|         },
 | |
|         height() {
 | |
|             if (height === undefined) {
 | |
|                 throw new Error(`Please call $("${selector}").set_height`);
 | |
|             }
 | |
|             return height;
 | |
|         },
 | |
|         hide() {
 | |
|             shown = false;
 | |
|             return self;
 | |
|         },
 | |
|         html(arg) {
 | |
|             if (arg !== undefined) {
 | |
|                 html = arg;
 | |
|                 return self;
 | |
|             }
 | |
|             return html;
 | |
|         },
 | |
|         is(arg) {
 | |
|             if (arg === ":visible") {
 | |
|                 return shown;
 | |
|             }
 | |
|             if (arg === ":focus") {
 | |
|                 return self.is_focused();
 | |
|             }
 | |
|             return self;
 | |
|         },
 | |
|         is_focused() {
 | |
|             // is_focused is not a jQuery thing; this is
 | |
|             // for our testing
 | |
|             return event_store.is_focused();
 | |
|         },
 | |
|         off(...args) {
 | |
|             event_store.off(...args);
 | |
|             return self;
 | |
|         },
 | |
|         offset() {
 | |
|             return {
 | |
|                 top: 0,
 | |
|                 left: 0,
 | |
|             };
 | |
|         },
 | |
|         on(...args) {
 | |
|             event_store.on(...args);
 | |
|             return self;
 | |
|         },
 | |
|         one(...args) {
 | |
|             event_store.one(...args);
 | |
|             return self;
 | |
|         },
 | |
|         parent() {
 | |
|             return my_parent;
 | |
|         },
 | |
|         parents(parents_selector) {
 | |
|             const result = parents_result.get(parents_selector);
 | |
|             assert.ok(
 | |
|                 result,
 | |
|                 "You need to call set_parents_result for " + parents_selector + " in " + selector,
 | |
|             );
 | |
|             return result;
 | |
|         },
 | |
|         prepend(arg) {
 | |
|             html = arg + html;
 | |
|             return self;
 | |
|         },
 | |
|         prop(name, val) {
 | |
|             if (val === undefined) {
 | |
|                 return properties.get(name);
 | |
|             }
 | |
|             properties.set(name, val);
 | |
|             return self;
 | |
|         },
 | |
|         removeAttr(name) {
 | |
|             attrs.delete(name);
 | |
|             return self;
 | |
|         },
 | |
|         removeClass(class_names) {
 | |
|             class_names = class_names.split(" ");
 | |
|             for (const class_name of class_names) {
 | |
|                 classes.delete(class_name);
 | |
|             }
 | |
|             return self;
 | |
|         },
 | |
|         remove() {
 | |
|             throw new Error(`
 | |
|                 We don't support remove in zjquery.
 | |
| 
 | |
|                 You can do $(...).remove = ... if necessary.
 | |
| 
 | |
|                 But you are probably writing too deep a test
 | |
|                 for node testing.
 | |
|             `);
 | |
|         },
 | |
|         removeData: noop,
 | |
|         replaceWith() {
 | |
|             return self;
 | |
|         },
 | |
|         scrollTop() {
 | |
|             return self;
 | |
|         },
 | |
|         serializeArray() {
 | |
|             return self;
 | |
|         },
 | |
|         set_find_results(find_selector, jquery_object) {
 | |
|             if (jquery_object === undefined) {
 | |
|                 throw new Error(
 | |
|                     "Please make the 'find result' be something like $.create('unused')",
 | |
|                 );
 | |
|             }
 | |
|             find_results.set(find_selector, jquery_object);
 | |
|         },
 | |
|         set_height(fake_height) {
 | |
|             height = fake_height;
 | |
|         },
 | |
|         set_parent(parent_elem) {
 | |
|             my_parent = parent_elem;
 | |
|         },
 | |
|         set_parents_result(selector, result) {
 | |
|             parents_result.set(selector, result);
 | |
|         },
 | |
|         show() {
 | |
|             shown = true;
 | |
|             return self;
 | |
|         },
 | |
|         slice() {
 | |
|             return self;
 | |
|         },
 | |
|         stop() {
 | |
|             return self;
 | |
|         },
 | |
|         text(...args) {
 | |
|             if (args.length !== 0) {
 | |
|                 if (args[0] !== undefined) {
 | |
|                     text = args[0].toString();
 | |
|                 }
 | |
|                 return self;
 | |
|             }
 | |
|             return text;
 | |
|         },
 | |
|         toggle(show) {
 | |
|             assert.ok([true, false].includes(show));
 | |
|             shown = show;
 | |
|             return self;
 | |
|         },
 | |
|         tooltip() {
 | |
|             return self;
 | |
|         },
 | |
|         trigger(ev) {
 | |
|             event_store.trigger(self, ev);
 | |
|             return self;
 | |
|         },
 | |
|         val(...args) {
 | |
|             if (args.length === 0) {
 | |
|                 return value || "";
 | |
|             }
 | |
|             [value] = args;
 | |
|             return self;
 | |
|         },
 | |
|         visible() {
 | |
|             return shown;
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     if (opts.children) {
 | |
|         self.map = (f) => opts.children.map((i, elem) => f(elem, i));
 | |
|         self.each = (f) => {
 | |
|             for (const child of opts.children) {
 | |
|                 f.call(child);
 | |
|             }
 | |
|         };
 | |
|         self[Symbol.iterator] = function* () {
 | |
|             for (const child of opts.children) {
 | |
|                 yield child;
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         for (const [i, child] of opts.children.entries()) {
 | |
|             self[i] = child;
 | |
|         }
 | |
| 
 | |
|         self.length = opts.children.length;
 | |
|     }
 | |
| 
 | |
|     if (selector[0] === "<") {
 | |
|         self.html(selector);
 | |
|     }
 | |
| 
 | |
|     self.selector = selector;
 | |
| 
 | |
|     self.__zjquery = true;
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| function make_event_store(selector) {
 | |
|     /*
 | |
| 
 | |
|        This function returns an event_store object that
 | |
|        simulates the behavior of .on and .off from jQuery.
 | |
| 
 | |
|        It also has methods to retrieve handlers that have
 | |
|        been set via .on (or similar methods), which can
 | |
|        be useful for tests that want to test the actual
 | |
|        handlers.
 | |
| 
 | |
|     */
 | |
|     const on_functions = new Map();
 | |
|     const child_on_functions = new Map();
 | |
|     let focused = false;
 | |
| 
 | |
|     const self = {
 | |
|         get_on_handler(name, child_selector) {
 | |
|             let handler;
 | |
| 
 | |
|             if (child_selector === undefined) {
 | |
|                 handler = on_functions.get(name);
 | |
|                 if (!handler) {
 | |
|                     throw new Error("no " + name + " handler for " + selector);
 | |
|                 }
 | |
|                 return handler;
 | |
|             }
 | |
| 
 | |
|             const child_on = child_on_functions.get(child_selector);
 | |
|             if (child_on) {
 | |
|                 handler = child_on.get(name);
 | |
|             }
 | |
| 
 | |
|             if (!handler) {
 | |
|                 throw new Error("no " + name + " handler for " + selector + " " + child_selector);
 | |
|             }
 | |
| 
 | |
|             return handler;
 | |
|         },
 | |
| 
 | |
|         off(event_name, ...args) {
 | |
|             if (args.length === 0) {
 | |
|                 on_functions.delete(event_name);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // In the Zulip codebase we never use this form of
 | |
|             // .off in code that we test: $(...).off('click', child_sel);
 | |
|             //
 | |
|             // So we don't support this for now.
 | |
|             throw new Error("zjquery does not support this call sequence");
 | |
|         },
 | |
| 
 | |
|         on(event_name, ...args) {
 | |
|             // parameters will either be
 | |
|             //    (event_name, handler) or
 | |
|             //    (event_name, sel, handler)
 | |
|             if (args.length === 1) {
 | |
|                 const [handler] = args;
 | |
|                 if (on_functions.has(event_name)) {
 | |
|                     console.info("\nEither the app or the test can be at fault here..");
 | |
|                     console.info("(sometimes you just want to call $.clear_all_elements();)\n");
 | |
|                     throw new Error("dup " + event_name + " handler for " + selector);
 | |
|                 }
 | |
| 
 | |
|                 on_functions.set(event_name, handler);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (args.length !== 2) {
 | |
|                 throw new Error("wrong number of arguments passed in");
 | |
|             }
 | |
| 
 | |
|             const [sel, handler] = args;
 | |
|             assert.equal(typeof sel, "string", "String selectors expected here.");
 | |
|             assert.equal(typeof handler, "function", "An handler function expected here.");
 | |
| 
 | |
|             if (!child_on_functions.has(sel)) {
 | |
|                 child_on_functions.set(sel, new Map());
 | |
|             }
 | |
| 
 | |
|             const child_on = child_on_functions.get(sel);
 | |
| 
 | |
|             if (child_on.has(event_name)) {
 | |
|                 throw new Error("dup " + event_name + " handler for " + selector + " " + sel);
 | |
|             }
 | |
| 
 | |
|             child_on.set(event_name, handler);
 | |
|         },
 | |
| 
 | |
|         one(event_name, handler) {
 | |
|             self.on(event_name, function (ev) {
 | |
|                 self.off(event_name);
 | |
|                 return handler.call(this, ev);
 | |
|             });
 | |
|         },
 | |
| 
 | |
|         trigger($element, ev, data) {
 | |
|             if (typeof ev === "string") {
 | |
|                 ev = new FakeEvent(ev);
 | |
|             }
 | |
|             if (!ev.target) {
 | |
|                 ev.target = $element;
 | |
|             }
 | |
|             const func = on_functions.get(ev.type);
 | |
| 
 | |
|             if (func) {
 | |
|                 // It's possible that test code will trigger events
 | |
|                 // that haven't been set up yet, but we are trying to
 | |
|                 // eventually deprecate trigger in our codebase, so for
 | |
|                 // now we just let calls to trigger silently do nothing.
 | |
|                 // (And I think actual jQuery would do the same thing.)
 | |
|                 func.call($element, ev, data);
 | |
|             }
 | |
| 
 | |
|             if (ev.type === "focus" || ev.type === "focusin") {
 | |
|                 focused = true;
 | |
|             } else if (ev.type === "blur" || ev.type === "focusout") {
 | |
|                 focused = false;
 | |
|             }
 | |
|         },
 | |
| 
 | |
|         is_focused() {
 | |
|             return focused;
 | |
|         },
 | |
|     };
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| module.exports = FakeElement;
 |