2019-11-02 00:06:25 +01:00
|
|
|
const noop = function () {};
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2018-04-16 21:45:29 +02:00
|
|
|
exports.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.
|
|
|
|
|
|
|
|
*/
|
2019-12-29 13:23:46 +01:00
|
|
|
const on_functions = new Map();
|
|
|
|
const child_on_functions = new Map();
|
2017-05-25 01:49:29 +02:00
|
|
|
|
2018-04-16 20:43:55 +02:00
|
|
|
function generic_event(event_name, arg) {
|
2018-06-05 08:12:06 +02:00
|
|
|
if (typeof arg === 'function') {
|
2018-04-16 20:43:55 +02:00
|
|
|
on_functions.set(event_name, arg);
|
|
|
|
} else {
|
2019-11-02 00:06:25 +01:00
|
|
|
const handler = on_functions.get(event_name);
|
2018-04-16 20:43:55 +02:00
|
|
|
if (!handler) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const error = 'Cannot find ' + event_name + ' handler for ' + selector;
|
2018-04-16 20:43:55 +02:00
|
|
|
throw Error(error);
|
|
|
|
}
|
|
|
|
handler(arg);
|
|
|
|
}
|
2018-04-12 14:45:06 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const self = {
|
2018-04-16 21:45:29 +02:00
|
|
|
generic_event: generic_event,
|
|
|
|
|
|
|
|
get_on_handler: function (name, child_selector) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let handler;
|
2018-04-16 21:45:29 +02:00
|
|
|
|
|
|
|
if (child_selector === undefined) {
|
2019-04-18 19:28:25 +02:00
|
|
|
handler = on_functions.get(name);
|
|
|
|
if (!handler) {
|
|
|
|
throw Error('no ' + name + ' handler for ' + selector);
|
|
|
|
}
|
|
|
|
return handler;
|
2018-04-16 21:45:29 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const child_on = child_on_functions.get(child_selector);
|
2019-04-18 19:28:25 +02:00
|
|
|
if (child_on) {
|
|
|
|
handler = child_on.get(name);
|
2018-04-16 21:45:29 +02:00
|
|
|
}
|
|
|
|
|
2019-04-18 19:28:25 +02:00
|
|
|
if (!handler) {
|
|
|
|
throw Error('no ' + name + ' handler for ' + selector + ' ' + child_selector);
|
|
|
|
}
|
|
|
|
|
|
|
|
return handler;
|
2018-04-16 21:45:29 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
off: function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const event_name = arguments[0];
|
2018-04-16 21:45:29 +02:00
|
|
|
|
2019-04-18 19:28:25 +02:00
|
|
|
if (arguments.length === 1) {
|
2019-12-29 13:23:46 +01:00
|
|
|
on_functions.delete(event_name);
|
2019-04-18 19:28:25 +02:00
|
|
|
return;
|
2018-04-16 21:45:29 +02:00
|
|
|
}
|
2019-04-18 19:28:25 +02:00
|
|
|
|
|
|
|
// 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 Error('zjquery does not support this call sequence');
|
2018-04-16 21:45:29 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
on: function () {
|
|
|
|
// parameters will either be
|
|
|
|
// (event_name, handler) or
|
|
|
|
// (event_name, sel, handler)
|
2019-11-02 00:06:25 +01:00
|
|
|
const event_name = arguments[0];
|
|
|
|
let handler;
|
2018-04-16 21:45:29 +02:00
|
|
|
|
|
|
|
if (arguments.length === 2) {
|
|
|
|
handler = arguments[1];
|
2019-04-18 19:28:25 +02:00
|
|
|
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 Error('dup ' + event_name + ' handler for ' + selector);
|
|
|
|
}
|
|
|
|
|
|
|
|
on_functions.set(event_name, handler);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (arguments.length !== 3) {
|
|
|
|
throw Error('wrong number of arguments passed in');
|
|
|
|
}
|
|
|
|
|
2019-10-26 00:11:05 +02:00
|
|
|
const sel = arguments[1];
|
2019-04-18 19:28:25 +02:00
|
|
|
handler = arguments[2];
|
|
|
|
assert.equal(typeof sel, 'string', 'String selectors expected here.');
|
|
|
|
assert.equal(typeof handler, 'function', 'An handler function expected here.');
|
2019-12-29 13:23:46 +01:00
|
|
|
|
|
|
|
if (!child_on_functions.has(sel)) {
|
|
|
|
child_on_functions.set(sel, new Map());
|
|
|
|
}
|
|
|
|
|
|
|
|
const child_on = child_on_functions.get(sel);
|
2019-04-18 19:28:25 +02:00
|
|
|
|
|
|
|
if (child_on.has(event_name)) {
|
|
|
|
throw Error('dup ' + event_name + ' handler for ' + selector + ' ' + sel);
|
2018-04-16 21:45:29 +02:00
|
|
|
}
|
2019-04-18 19:28:25 +02:00
|
|
|
|
|
|
|
child_on.set(event_name, handler);
|
2018-04-16 21:45:29 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
trigger: function (ev) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const ev_name = typeof ev === 'string' ? ev : ev.name;
|
|
|
|
const func = on_functions.get(ev_name);
|
2019-04-18 19:28:25 +02:00
|
|
|
|
|
|
|
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.)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
func(ev.data);
|
2018-04-16 21:45:29 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
return self;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.make_new_elem = function (selector, opts) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let html = 'never-been-set';
|
|
|
|
let text = 'never-been-set';
|
|
|
|
let value;
|
|
|
|
let css;
|
|
|
|
let shown = false;
|
|
|
|
let focused = false;
|
2019-12-29 13:23:46 +01:00
|
|
|
const find_results = new Map();
|
2019-11-02 00:06:25 +01:00
|
|
|
let my_parent;
|
2019-12-29 13:23:46 +01:00
|
|
|
const parents_result = new Map();
|
|
|
|
const properties = new Map();
|
|
|
|
const attrs = new Map();
|
|
|
|
const classes = new Map();
|
2019-11-02 00:06:25 +01:00
|
|
|
const event_store = exports.make_event_store(selector);
|
2018-04-16 21:45:29 +02:00
|
|
|
|
2019-10-26 00:26:37 +02:00
|
|
|
const self = {
|
2018-04-16 20:43:55 +02:00
|
|
|
addClass: function (class_name) {
|
|
|
|
classes.set(class_name, true);
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
attr: function (name, val) {
|
|
|
|
if (val === undefined) {
|
|
|
|
return attrs.get(name);
|
|
|
|
}
|
|
|
|
attrs.set(name, val);
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
blur: function () {
|
|
|
|
focused = false;
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
click: function (arg) {
|
2018-04-16 21:45:29 +02:00
|
|
|
event_store.generic_event('click', arg);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
2019-05-22 01:49:54 +02:00
|
|
|
closest: function (selector) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = self;
|
|
|
|
const search = selector.startsWith('.') || selector.startsWith('#') ? selector.substring(1) : selector;
|
js: Convert a.indexOf(…) !== -1 to a.includes(…).
Babel polyfills this for us for Internet Explorer.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
recast.visit(ast, {
visitBinaryExpression(path) {
const { operator, left, right } = path.node;
if (
n.CallExpression.check(left) &&
n.MemberExpression.check(left.callee) &&
!left.callee.computed &&
n.Identifier.check(left.callee.property) &&
left.callee.property.name === "indexOf" &&
left.arguments.length === 1 &&
checkExpression(left.arguments[0]) &&
((["===", "!==", "==", "!=", ">", "<="].includes(operator) &&
n.UnaryExpression.check(right) &&
right.operator == "-" &&
n.Literal.check(right.argument) &&
right.argument.value === 1) ||
([">=", "<"].includes(operator) &&
n.Literal.check(right) &&
right.value === 0))
) {
const test = b.callExpression(
b.memberExpression(left.callee.object, b.identifier("includes")),
[left.arguments[0]]
);
path.replace(
["!==", "!=", ">", ">="].includes(operator)
? test
: b.unaryExpression("!", test)
);
changed = true;
}
this.traverse(path);
},
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-08 04:55:06 +01:00
|
|
|
if (elem.selector.includes(search)) {
|
2019-05-22 01:49:54 +02:00
|
|
|
return elem;
|
|
|
|
} else if (parents_result.get(selector)) {
|
|
|
|
return parents_result.get(selector);
|
|
|
|
} else if (!elem.parent()) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return elem.parent().closest(selector);
|
|
|
|
},
|
2020-01-03 17:10:59 +01:00
|
|
|
data: function (name, val) {
|
|
|
|
if (val === undefined) {
|
|
|
|
const data_val = attrs.get('data-' + name);
|
|
|
|
if (data_val === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return JSON.parse(data_val);
|
|
|
|
}
|
|
|
|
attrs.set('data-' + name, val);
|
|
|
|
return self;
|
|
|
|
},
|
2018-08-22 00:31:48 +02:00
|
|
|
delay: function () {
|
|
|
|
return self;
|
|
|
|
},
|
2018-04-16 20:43:55 +02:00
|
|
|
debug: function () {
|
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
shown: shown,
|
|
|
|
selector: selector,
|
|
|
|
};
|
|
|
|
},
|
2018-07-03 11:03:24 +02:00
|
|
|
empty: function (arg) {
|
|
|
|
if (arg === undefined) {
|
|
|
|
find_results.clear();
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
},
|
2018-04-16 20:43:55 +02:00
|
|
|
eq: function () {
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
expectOne: function () {
|
|
|
|
// silently do nothing
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
fadeTo: noop,
|
|
|
|
find: function (child_selector) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const child = find_results.get(child_selector);
|
2018-04-16 20:43:55 +02:00
|
|
|
if (child) {
|
|
|
|
return child;
|
|
|
|
}
|
|
|
|
if (opts.silent) {
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
throw Error("Cannot find " + child_selector + " in " + selector);
|
|
|
|
},
|
|
|
|
focus: function () {
|
|
|
|
focused = true;
|
|
|
|
return self;
|
|
|
|
},
|
2018-07-23 00:25:21 +02:00
|
|
|
focusin: function () {
|
|
|
|
focused = true;
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
focusout: function () {
|
|
|
|
focused = false;
|
2019-01-06 00:29:31 +01:00
|
|
|
return self;
|
2018-07-23 00:25:21 +02:00
|
|
|
},
|
2018-04-16 20:43:55 +02:00
|
|
|
get: function (idx) {
|
|
|
|
// We have some legacy code that does $('foo').get(0).
|
|
|
|
assert.equal(idx, 0);
|
|
|
|
return selector;
|
|
|
|
},
|
|
|
|
get_on_handler: function (name, child_selector) {
|
2018-04-16 21:45:29 +02:00
|
|
|
return event_store.get_on_handler(name, child_selector);
|
2018-04-16 20:43:55 +02:00
|
|
|
},
|
|
|
|
hasClass: function (class_name) {
|
|
|
|
return classes.has(class_name);
|
|
|
|
},
|
|
|
|
height: noop,
|
|
|
|
hide: function () {
|
|
|
|
shown = false;
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
html: function (arg) {
|
|
|
|
if (arg !== undefined) {
|
|
|
|
html = arg;
|
2017-07-08 17:41:53 +02:00
|
|
|
return self;
|
2018-04-16 20:43:55 +02:00
|
|
|
}
|
|
|
|
return html;
|
|
|
|
},
|
|
|
|
is: function (arg) {
|
|
|
|
if (arg === ':visible') {
|
|
|
|
return shown;
|
|
|
|
}
|
2018-04-21 14:59:03 +02:00
|
|
|
if (arg === ':focus') {
|
|
|
|
return focused;
|
|
|
|
}
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
is_focused: function () {
|
|
|
|
// is_focused is not a jQuery thing; this is
|
|
|
|
// for our testing
|
|
|
|
return focused;
|
|
|
|
},
|
|
|
|
keydown: function (arg) {
|
2018-04-16 21:45:29 +02:00
|
|
|
event_store.generic_event('keydown', arg);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
keyup: function (arg) {
|
2018-04-16 21:45:29 +02:00
|
|
|
event_store.generic_event('keyup', arg);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
off: function () {
|
2018-04-16 21:45:29 +02:00
|
|
|
event_store.off.apply(undefined, arguments);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
on: function () {
|
2018-04-16 21:45:29 +02:00
|
|
|
event_store.on.apply(undefined, arguments);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
parent: function () {
|
|
|
|
return my_parent;
|
|
|
|
},
|
|
|
|
parents: function (parents_selector) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const result = parents_result.get(parents_selector);
|
2018-04-16 20:43:55 +02:00
|
|
|
assert(result, 'You need to call set_parents_result for ' +
|
|
|
|
parents_selector + ' in ' + selector);
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
prop: function (name, val) {
|
|
|
|
if (val === undefined) {
|
|
|
|
return properties.get(name);
|
|
|
|
}
|
|
|
|
properties.set(name, val);
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
removeAttr: function (name) {
|
2019-12-29 13:23:46 +01:00
|
|
|
attrs.delete(name);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
2018-04-18 08:53:14 +02:00
|
|
|
removeClass: function (class_names) {
|
|
|
|
class_names = class_names.split(' ');
|
|
|
|
class_names.forEach(function (class_name) {
|
2019-12-29 13:23:46 +01:00
|
|
|
classes.delete(class_name);
|
2018-04-18 08:53:14 +02:00
|
|
|
});
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
remove: function () {
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
removeData: noop,
|
|
|
|
replaceWith: function () {
|
|
|
|
return self;
|
|
|
|
},
|
2019-02-27 12:52:05 +01:00
|
|
|
scrollTop: function () {
|
|
|
|
return self;
|
|
|
|
},
|
2018-04-16 20:43:55 +02:00
|
|
|
select: function (arg) {
|
2018-04-16 21:45:29 +02:00
|
|
|
event_store.generic_event('select', arg);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
set_find_results: function (find_selector, jquery_object) {
|
|
|
|
find_results.set(find_selector, jquery_object);
|
|
|
|
},
|
|
|
|
show: function () {
|
|
|
|
shown = true;
|
|
|
|
return self;
|
|
|
|
},
|
2019-02-27 12:52:05 +01:00
|
|
|
serializeArray: function () {
|
|
|
|
return self;
|
|
|
|
},
|
2018-04-16 20:43:55 +02:00
|
|
|
set_parent: function (parent_elem) {
|
|
|
|
my_parent = parent_elem;
|
|
|
|
},
|
|
|
|
set_parents_result: function (selector, result) {
|
|
|
|
parents_result.set(selector, result);
|
|
|
|
},
|
|
|
|
stop: function () {
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
text: function (arg) {
|
|
|
|
if (arg !== undefined) {
|
|
|
|
text = arg;
|
2018-03-25 15:28:49 +02:00
|
|
|
return self;
|
2018-04-16 20:43:55 +02:00
|
|
|
}
|
|
|
|
return text;
|
|
|
|
},
|
|
|
|
trigger: function (ev) {
|
2018-04-16 21:45:29 +02:00
|
|
|
event_store.trigger(ev);
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
},
|
|
|
|
val: function () {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
return value || '';
|
|
|
|
}
|
|
|
|
value = arguments[0];
|
|
|
|
return self;
|
|
|
|
},
|
2018-07-23 00:25:21 +02:00
|
|
|
css: function () {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
return css || {};
|
|
|
|
}
|
|
|
|
css = arguments[0];
|
|
|
|
return self;
|
|
|
|
},
|
2018-04-16 20:43:55 +02:00
|
|
|
visible: function () {
|
|
|
|
return shown;
|
|
|
|
},
|
|
|
|
};
|
2017-07-06 00:18:15 +02:00
|
|
|
|
2018-04-16 20:43:55 +02:00
|
|
|
if (selector[0] === '<') {
|
|
|
|
self.html(selector);
|
|
|
|
}
|
2017-07-06 15:47:25 +02:00
|
|
|
|
2018-04-16 20:43:55 +02:00
|
|
|
self[0] = 'you-must-set-the-child-yourself';
|
2017-06-14 14:17:03 +02:00
|
|
|
|
2018-04-16 20:43:55 +02:00
|
|
|
self.selector = selector;
|
|
|
|
|
2019-05-22 01:54:53 +02:00
|
|
|
self.length = 1;
|
|
|
|
|
2018-04-16 20:43:55 +02:00
|
|
|
return self;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.make_zjquery = function (opts) {
|
|
|
|
opts = opts || {};
|
2017-06-14 14:17:03 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let elems = {};
|
2018-04-16 20:43:55 +02:00
|
|
|
|
|
|
|
// Our fn structure helps us simulate extending jQuery.
|
2019-11-02 00:06:25 +01:00
|
|
|
const fn = {};
|
2017-05-25 01:49:29 +02:00
|
|
|
|
2018-04-16 20:43:55 +02:00
|
|
|
function new_elem(selector) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = exports.make_new_elem(selector, {
|
2018-04-16 20:43:55 +02:00
|
|
|
silent: opts.silent,
|
|
|
|
});
|
2020-02-06 03:52:12 +01:00
|
|
|
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 = {
|
2019-04-18 21:11:30 +02:00
|
|
|
get: (target, key) => {
|
|
|
|
// Handle the special case of equality checks, which
|
|
|
|
// we can infer by assert.equal trying to access the
|
|
|
|
// "stack" key.
|
|
|
|
if (key === 'stack') {
|
2019-11-02 00:06:25 +01:00
|
|
|
const error = '\nInstead of doing equality checks on a full object, ' +
|
2019-04-18 21:11:30 +02:00
|
|
|
'do `assert_equal(foo.selector, ".some_class")\n';
|
|
|
|
throw Error(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
const val = target[key];
|
|
|
|
|
|
|
|
if (val === undefined) {
|
|
|
|
// 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.
|
|
|
|
if (typeof key === 'symbol') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (key === 'inspect') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw Error('You must create a stub for $("' + selector + '").' + key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-11-02 00:06:25 +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
|
|
|
|
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") {
|
|
|
|
// If somebody is passing us a function, we emulate
|
|
|
|
// jQuery's behavior of running this function after
|
|
|
|
// page load time. But there are no pages to load,
|
|
|
|
// so we just call it right away.
|
|
|
|
arg();
|
|
|
|
return;
|
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).
|
|
|
|
if (arg.selector) {
|
|
|
|
if (elems[arg.selector]) {
|
|
|
|
return arg;
|
2018-04-12 17:37:34 +02:00
|
|
|
}
|
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_$) {
|
|
|
|
assert(typeof arg.to_$ === "function");
|
|
|
|
return arg.to_$();
|
|
|
|
}
|
|
|
|
|
2018-04-05 16:46:45 +02:00
|
|
|
if (arg2 !== undefined) {
|
|
|
|
throw Error("We only use one-argument variations of $(...) in Zulip code.");
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const selector = arg;
|
2017-07-08 15:16:19 +02:00
|
|
|
|
2018-04-12 18:06:39 +02:00
|
|
|
if (typeof selector !== "string") {
|
|
|
|
console.info(arg);
|
|
|
|
throw Error("zjquery does not know how to wrap this object yet");
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const valid_selector =
|
js: Convert a.indexOf(…) !== -1 to a.includes(…).
Babel polyfills this for us for Internet Explorer.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
recast.visit(ast, {
visitBinaryExpression(path) {
const { operator, left, right } = path.node;
if (
n.CallExpression.check(left) &&
n.MemberExpression.check(left.callee) &&
!left.callee.computed &&
n.Identifier.check(left.callee.property) &&
left.callee.property.name === "indexOf" &&
left.arguments.length === 1 &&
checkExpression(left.arguments[0]) &&
((["===", "!==", "==", "!=", ">", "<="].includes(operator) &&
n.UnaryExpression.check(right) &&
right.operator == "-" &&
n.Literal.check(right.argument) &&
right.argument.value === 1) ||
([">=", "<"].includes(operator) &&
n.Literal.check(right) &&
right.value === 0))
) {
const test = b.callExpression(
b.memberExpression(left.callee.object, b.identifier("includes")),
[left.arguments[0]]
);
path.replace(
["!==", "!=", ">", ">="].includes(operator)
? test
: b.unaryExpression("!", test)
);
changed = true;
}
this.traverse(path);
},
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-08 04:55:06 +01:00
|
|
|
'<#.'.includes(selector[0]) ||
|
2018-06-06 18:50:09 +02:00
|
|
|
selector === 'window-stub' ||
|
|
|
|
selector === 'document-stub' ||
|
|
|
|
selector === 'body' ||
|
|
|
|
selector === 'html' ||
|
2018-06-06 18:19:09 +02:00
|
|
|
selector.location ||
|
js: Convert a.indexOf(…) !== -1 to a.includes(…).
Babel polyfills this for us for Internet Explorer.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
recast.visit(ast, {
visitBinaryExpression(path) {
const { operator, left, right } = path.node;
if (
n.CallExpression.check(left) &&
n.MemberExpression.check(left.callee) &&
!left.callee.computed &&
n.Identifier.check(left.callee.property) &&
left.callee.property.name === "indexOf" &&
left.arguments.length === 1 &&
checkExpression(left.arguments[0]) &&
((["===", "!==", "==", "!=", ">", "<="].includes(operator) &&
n.UnaryExpression.check(right) &&
right.operator == "-" &&
n.Literal.check(right.argument) &&
right.argument.value === 1) ||
([">=", "<"].includes(operator) &&
n.Literal.check(right) &&
right.value === 0))
) {
const test = b.callExpression(
b.memberExpression(left.callee.object, b.identifier("includes")),
[left.arguments[0]]
);
path.replace(
["!==", "!=", ">", ">="].includes(operator)
? test
: b.unaryExpression("!", test)
);
changed = true;
}
this.traverse(path);
},
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-08 04:55:06 +01:00
|
|
|
selector.includes('#') ||
|
|
|
|
selector.includes('.') ||
|
|
|
|
selector.includes('[') && selector.indexOf(']') >= selector.indexOf('[');
|
2017-07-08 15:16:19 +02:00
|
|
|
|
|
|
|
assert(valid_selector,
|
|
|
|
'Invalid selector: ' + selector +
|
|
|
|
' Use $.create() maybe?');
|
|
|
|
|
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
if (elems[selector] === undefined) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = new_elem(selector);
|
2017-07-08 17:41:53 +02:00
|
|
|
elems[selector] = elem;
|
2017-05-24 03:34:18 +02:00
|
|
|
}
|
2017-05-25 01:49:29 +02:00
|
|
|
return elems[selector];
|
|
|
|
};
|
2017-05-24 03:34:18 +02:00
|
|
|
|
2017-07-08 15:16:19 +02:00
|
|
|
zjquery.create = function (name) {
|
|
|
|
assert(!elems[name],
|
|
|
|
'You already created an object with this name!!');
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = new_elem(name);
|
2017-07-08 17:41:53 +02:00
|
|
|
elems[name] = elem;
|
2017-07-08 15:16:19 +02:00
|
|
|
return elems[name];
|
|
|
|
};
|
2017-05-24 03:05:29 +02:00
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
zjquery.stub_selector = function (selector, stub) {
|
|
|
|
elems[selector] = stub;
|
|
|
|
};
|
2017-05-24 22:31:12 +02:00
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
zjquery.trim = function (s) { return s; };
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
zjquery.state = function () {
|
|
|
|
// useful for debugging
|
2019-11-02 00:06:25 +01:00
|
|
|
let res = _.map(elems, function (v) {
|
2017-05-25 01:49:29 +02:00
|
|
|
return v.debug();
|
|
|
|
});
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
res = _.map(res, function (v) {
|
|
|
|
return [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
|
|
|
|
2017-06-14 20:28:55 +02:00
|
|
|
zjquery.Event = function (name, data) {
|
|
|
|
return {
|
|
|
|
name: name,
|
|
|
|
data: data,
|
|
|
|
};
|
|
|
|
};
|
2017-05-23 03:20:15 +02:00
|
|
|
|
2017-06-22 23:40:39 +02:00
|
|
|
zjquery.extend = function (content, container) {
|
|
|
|
return _.extend(content, container);
|
|
|
|
};
|
|
|
|
|
2018-04-12 14:45:06 +02:00
|
|
|
zjquery.fn = fn;
|
|
|
|
|
2018-07-05 14:58:14 +02:00
|
|
|
zjquery.clear_all_elements = function () {
|
|
|
|
elems = {};
|
|
|
|
};
|
|
|
|
|
2017-05-25 01:49:29 +02:00
|
|
|
return zjquery;
|
|
|
|
};
|