2020-04-13 13:52:07 +02:00
|
|
|
zrequire('scroll_util');
|
2018-06-21 06:58:19 +02:00
|
|
|
zrequire('list_render');
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
// We need these stubs to get by instanceof checks.
|
|
|
|
// The list_render library allows you to insert objects
|
|
|
|
// that are either jQuery, Element, or just raw HTML
|
|
|
|
// strings. We initially test with raw strings.
|
|
|
|
set_global('jQuery', 'stub');
|
|
|
|
set_global('Element', function () {
|
|
|
|
return { };
|
|
|
|
});
|
|
|
|
|
|
|
|
// We only need very simple jQuery wrappers for when the
|
|
|
|
// "real" code wraps html or sets up click handlers.
|
|
|
|
// We'll simulate most other objects ourselves.
|
|
|
|
set_global('$', (arg) => {
|
|
|
|
if (arg.to_jquery) {
|
|
|
|
return arg.to_jquery();
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
html: () => arg,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2020-04-24 12:20:45 +02:00
|
|
|
// We build objects here that simulate jQuery containers.
|
|
|
|
// The main thing to do at first is simulate that our
|
|
|
|
// scroll container is the nearest ancestor to our main
|
|
|
|
// container that has a max-height attribute, and then
|
|
|
|
// the scroll container will have a scroll event attached to
|
|
|
|
// it. This is a good time to read set_up_event_handlers
|
|
|
|
// in the real code.
|
|
|
|
|
|
|
|
function make_container() {
|
2018-04-17 21:29:21 +02:00
|
|
|
const container = {};
|
|
|
|
|
|
|
|
container.length = () => 1;
|
|
|
|
container.is = () => false;
|
|
|
|
container.css = (prop) => {
|
|
|
|
assert.equal(prop, 'max-height');
|
|
|
|
return 'none';
|
|
|
|
};
|
|
|
|
|
2020-04-24 12:20:45 +02:00
|
|
|
// Make our append function just set a field we can
|
|
|
|
// check in our tests.
|
|
|
|
container.append = (data) => {
|
|
|
|
container.appended_data = data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return container;
|
|
|
|
}
|
|
|
|
|
|
|
|
function make_scroll_container(container) {
|
|
|
|
const scroll_container = {};
|
|
|
|
scroll_container.is = () => false;
|
|
|
|
scroll_container.length = () => 1;
|
|
|
|
scroll_container.css = (prop) => {
|
2018-04-17 21:29:21 +02:00
|
|
|
assert.equal(prop, 'max-height');
|
|
|
|
return 100;
|
|
|
|
};
|
|
|
|
|
2020-04-24 13:22:32 +02:00
|
|
|
scroll_container.cleared = false;
|
|
|
|
|
2018-04-17 21:29:21 +02:00
|
|
|
// Capture the scroll callback so we can call it in
|
|
|
|
// our tests.
|
2020-04-24 12:20:45 +02:00
|
|
|
scroll_container.on = (ev, f) => {
|
|
|
|
assert.equal(ev, 'scroll.list_widget_container');
|
|
|
|
scroll_container.call_scroll = () => {
|
|
|
|
f.call(scroll_container);
|
2018-04-17 21:29:21 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2020-04-24 13:22:32 +02:00
|
|
|
scroll_container.off = (ev) => {
|
|
|
|
assert.equal(ev, 'scroll.list_widget_container');
|
|
|
|
scroll_container.cleared = true;
|
|
|
|
};
|
|
|
|
|
2020-04-24 12:20:45 +02:00
|
|
|
container.parent = () => scroll_container;
|
2018-04-17 21:29:21 +02:00
|
|
|
|
2020-04-24 12:20:45 +02:00
|
|
|
return scroll_container;
|
2018-04-17 21:29:21 +02:00
|
|
|
}
|
|
|
|
|
2020-04-24 12:30:56 +02:00
|
|
|
function make_sort_container() {
|
|
|
|
const sort_container = {};
|
|
|
|
|
2020-04-24 13:22:32 +02:00
|
|
|
sort_container.cleared = false;
|
|
|
|
|
2020-04-24 12:30:56 +02:00
|
|
|
sort_container.on = (ev, sel, f) => {
|
|
|
|
assert.equal(ev, 'click.list_widget_sort');
|
|
|
|
assert.equal(sel, '[data-sort]');
|
|
|
|
sort_container.f = f;
|
|
|
|
};
|
|
|
|
|
2020-04-24 13:22:32 +02:00
|
|
|
sort_container.off = (ev) => {
|
|
|
|
assert.equal(ev, 'click.list_widget_sort');
|
|
|
|
sort_container.cleared = true;
|
|
|
|
};
|
|
|
|
|
2020-04-24 12:30:56 +02:00
|
|
|
return sort_container;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:59:33 +02:00
|
|
|
function make_filter_element() {
|
|
|
|
const element = {};
|
|
|
|
|
2020-04-24 13:22:32 +02:00
|
|
|
element.cleared = false;
|
|
|
|
|
2020-04-24 13:59:33 +02:00
|
|
|
element.on = (ev, f) => {
|
|
|
|
assert.equal(ev, 'input.list_widget_filter');
|
|
|
|
element.f = f;
|
|
|
|
};
|
|
|
|
|
2020-04-24 13:22:32 +02:00
|
|
|
element.off = (ev) => {
|
|
|
|
assert.equal(ev, 'input.list_widget_filter');
|
|
|
|
element.cleared = true;
|
|
|
|
};
|
|
|
|
|
2020-04-24 13:59:33 +02:00
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
2018-04-17 21:29:21 +02:00
|
|
|
function make_search_input() {
|
|
|
|
const $element = {};
|
|
|
|
|
|
|
|
// Allow ourselves to be wrapped by $(...) and
|
|
|
|
// return ourselves.
|
|
|
|
$element.to_jquery = () => $element;
|
|
|
|
|
|
|
|
$element.on = (event_name, f) => {
|
2020-04-14 12:56:54 +02:00
|
|
|
assert.equal(event_name, 'input.list_widget_filter');
|
2018-04-17 21:29:21 +02:00
|
|
|
$element.simulate_input_event = () => {
|
|
|
|
const elem = {
|
|
|
|
value: $element.val(),
|
|
|
|
};
|
|
|
|
f.call(elem);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
return $element;
|
|
|
|
}
|
|
|
|
|
|
|
|
function div(item) {
|
|
|
|
return '<div>' + item + '</div>';
|
|
|
|
}
|
|
|
|
|
2020-04-12 19:45:33 +02:00
|
|
|
run_test('scrolling', () => {
|
2020-04-24 12:20:45 +02:00
|
|
|
const container = make_container();
|
|
|
|
const scroll_container = make_scroll_container(container);
|
2018-04-17 21:29:21 +02:00
|
|
|
|
2020-04-12 19:45:33 +02:00
|
|
|
const items = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < 200; i += 1) {
|
|
|
|
items.push('item ' + i);
|
|
|
|
}
|
|
|
|
|
|
|
|
const opts = {
|
|
|
|
modifier: (item) => item,
|
|
|
|
};
|
|
|
|
|
|
|
|
container.html = (html) => { assert.equal(html, ''); };
|
2020-04-11 16:23:29 +02:00
|
|
|
list_render.create(container, items, opts);
|
2020-04-12 19:45:33 +02:00
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
container.appended_data.html(),
|
|
|
|
items.slice(0, 80).join('')
|
|
|
|
);
|
|
|
|
|
|
|
|
// Set up our fake geometry so it forces a scroll action.
|
2020-04-24 12:20:45 +02:00
|
|
|
scroll_container.scrollTop = 180;
|
|
|
|
scroll_container.clientHeight = 100;
|
|
|
|
scroll_container.scrollHeight = 260;
|
2020-04-12 19:45:33 +02:00
|
|
|
|
|
|
|
// Scrolling gets the next two elements from the list into
|
|
|
|
// our widget.
|
2020-04-24 12:20:45 +02:00
|
|
|
scroll_container.call_scroll();
|
2020-04-12 19:45:33 +02:00
|
|
|
assert.deepEqual(
|
|
|
|
container.appended_data.html(),
|
|
|
|
items.slice(80, 100).join('')
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
run_test('filtering', () => {
|
2020-04-24 12:20:45 +02:00
|
|
|
const container = make_container();
|
|
|
|
make_scroll_container(container);
|
2020-04-12 19:45:33 +02:00
|
|
|
|
2018-04-17 21:29:21 +02:00
|
|
|
const search_input = make_search_input();
|
|
|
|
|
|
|
|
const list = [
|
|
|
|
'apple',
|
|
|
|
'banana',
|
|
|
|
'carrot',
|
|
|
|
'dog',
|
|
|
|
'egg',
|
|
|
|
'fence',
|
|
|
|
'grape',
|
|
|
|
];
|
2020-04-24 22:16:39 +02:00
|
|
|
const opts = {
|
2018-04-17 21:29:21 +02:00
|
|
|
filter: {
|
|
|
|
element: search_input,
|
2019-12-30 17:44:24 +01:00
|
|
|
predicate: (item, value) => {
|
2020-02-08 04:04:36 +01:00
|
|
|
return item.includes(value);
|
2019-12-30 16:13:42 +01:00
|
|
|
},
|
2018-04-17 21:29:21 +02:00
|
|
|
},
|
|
|
|
modifier: (item) => div(item),
|
|
|
|
};
|
|
|
|
|
2020-04-12 19:45:33 +02:00
|
|
|
container.html = (html) => { assert.equal(html, ''); };
|
2020-04-24 22:16:39 +02:00
|
|
|
const widget = list_render.create(container, list, opts);
|
2018-04-17 21:29:21 +02:00
|
|
|
|
2020-04-12 19:45:33 +02:00
|
|
|
let expected_html =
|
|
|
|
'<div>apple</div>' +
|
|
|
|
'<div>banana</div>' +
|
|
|
|
'<div>carrot</div>' +
|
|
|
|
'<div>dog</div>' +
|
|
|
|
'<div>egg</div>' +
|
|
|
|
'<div>fence</div>' +
|
|
|
|
'<div>grape</div>';
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
|
|
|
|
|
|
|
// Filtering will pick out dog/egg/grape when we put "g"
|
|
|
|
// into our search input. (This uses the default filter, which
|
|
|
|
// is a glorified indexOf call.)
|
|
|
|
search_input.val = () => 'g';
|
|
|
|
search_input.simulate_input_event();
|
|
|
|
expected_html = '<div>dog</div><div>egg</div><div>grape</div>';
|
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
|
|
|
|
|
|
|
// We can insert new data into the widget.
|
|
|
|
const new_data = [
|
|
|
|
'greta',
|
|
|
|
'faye',
|
|
|
|
'gary',
|
|
|
|
'frank',
|
|
|
|
'giraffe',
|
|
|
|
'fox',
|
|
|
|
];
|
|
|
|
|
2020-04-15 01:29:34 +02:00
|
|
|
widget.replace_list_data(new_data);
|
2020-04-12 19:45:33 +02:00
|
|
|
expected_html =
|
|
|
|
'<div>greta</div>' +
|
|
|
|
'<div>gary</div>' +
|
|
|
|
'<div>giraffe</div>';
|
2018-04-17 21:29:21 +02:00
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
2020-04-24 22:16:39 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
run_test('no filtering', () => {
|
|
|
|
const container = make_container();
|
|
|
|
make_scroll_container(container);
|
|
|
|
container.html = () => {};
|
2020-03-10 19:48:03 +01:00
|
|
|
|
|
|
|
// Opts does not require a filter key.
|
2020-04-24 22:16:39 +02:00
|
|
|
const opts = {
|
2020-03-10 19:48:03 +01:00
|
|
|
modifier: (item) => div(item),
|
|
|
|
};
|
2020-04-24 22:16:39 +02:00
|
|
|
const widget = list_render.create(container, ['apple', 'banana'], opts);
|
2020-03-10 19:48:03 +01:00
|
|
|
widget.render();
|
|
|
|
|
2020-04-24 22:16:39 +02:00
|
|
|
const expected_html =
|
2020-04-12 19:45:33 +02:00
|
|
|
'<div>apple</div>' +
|
|
|
|
'<div>banana</div>';
|
2020-03-10 19:48:03 +01:00
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
function sort_button(opts) {
|
|
|
|
// The complications here are due to needing to find
|
|
|
|
// the list via complicated HTML assumptions. Also, we
|
|
|
|
// don't have any abstraction for the button and its
|
|
|
|
// siblings other than direct jQuery actions.
|
|
|
|
|
|
|
|
function data(sel) {
|
|
|
|
switch (sel) {
|
2018-05-07 03:30:13 +02:00
|
|
|
case "sort": return opts.sort_type;
|
|
|
|
case "sort-prop": return opts.prop_name;
|
|
|
|
default: throw Error('unknown selector: ' + sel);
|
2018-04-17 21:29:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function lookup(sel, value) {
|
|
|
|
return (selector) => {
|
|
|
|
assert.equal(sel, selector);
|
|
|
|
return value;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-04-24 16:13:42 +02:00
|
|
|
const classList = new Set();
|
|
|
|
|
2020-04-15 12:22:23 +02:00
|
|
|
const button = {
|
2018-04-17 21:29:21 +02:00
|
|
|
data: data,
|
2019-08-22 06:23:41 +02:00
|
|
|
closest: lookup('.progressive-table-wrapper', {
|
|
|
|
data: lookup('list-render', opts.list_name),
|
2018-04-17 21:29:21 +02:00
|
|
|
}),
|
2020-04-24 16:13:42 +02:00
|
|
|
addClass: (cls) => {
|
|
|
|
classList.add(cls);
|
|
|
|
},
|
|
|
|
hasClass: (cls) => {
|
|
|
|
return classList.has(cls);
|
|
|
|
},
|
|
|
|
removeClass: (cls) => {
|
|
|
|
classList.delete(cls);
|
2020-04-13 16:13:06 +02:00
|
|
|
},
|
2018-04-17 21:29:21 +02:00
|
|
|
siblings: lookup('.active', {
|
2020-04-24 16:13:42 +02:00
|
|
|
removeClass: (cls) => {
|
|
|
|
assert.equal(cls, 'active');
|
2018-04-17 21:29:21 +02:00
|
|
|
button.siblings_deactivated = true;
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
siblings_deactivated: false,
|
2020-04-24 12:30:56 +02:00
|
|
|
to_jquery: () => button,
|
2018-04-17 21:29:21 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
return button;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:59:33 +02:00
|
|
|
run_test('wire up filter element', () => {
|
2019-12-30 16:13:42 +01:00
|
|
|
const lst = [
|
|
|
|
'alice',
|
|
|
|
'JESSE',
|
2020-04-24 13:59:33 +02:00
|
|
|
'moses',
|
2019-12-30 16:13:42 +01:00
|
|
|
'scott',
|
2020-04-24 13:59:33 +02:00
|
|
|
'Sean',
|
2019-12-30 16:13:42 +01:00
|
|
|
'Xavier',
|
|
|
|
];
|
|
|
|
|
2020-04-24 13:59:33 +02:00
|
|
|
const container = make_container();
|
|
|
|
make_scroll_container(container);
|
|
|
|
const filter_element = make_filter_element();
|
|
|
|
|
|
|
|
// We don't care about what gets drawn initially.
|
|
|
|
container.html = () => {};
|
|
|
|
|
2019-12-30 16:13:42 +01:00
|
|
|
const opts = {
|
|
|
|
filter: {
|
2020-04-24 13:59:33 +02:00
|
|
|
filterer: (list, value) => {
|
|
|
|
return list.filter((item) => {
|
|
|
|
return item.toLowerCase().includes(value);
|
|
|
|
});
|
2019-12-30 16:13:42 +01:00
|
|
|
},
|
2020-04-24 13:59:33 +02:00
|
|
|
element: filter_element,
|
2019-12-30 16:13:42 +01:00
|
|
|
},
|
2020-04-24 13:59:33 +02:00
|
|
|
modifier: (s) => '(' + s + ')',
|
2019-12-30 16:13:42 +01:00
|
|
|
};
|
|
|
|
|
2020-04-24 13:59:33 +02:00
|
|
|
list_render.create(container, lst, opts);
|
|
|
|
filter_element.f.apply({value: 'se'});
|
|
|
|
assert.equal(
|
|
|
|
container.appended_data.html(),
|
|
|
|
'(JESSE)(moses)(Sean)'
|
|
|
|
);
|
2019-12-30 16:13:42 +01:00
|
|
|
});
|
|
|
|
|
2018-05-15 12:40:07 +02:00
|
|
|
run_test('sorting', () => {
|
2020-04-24 12:20:45 +02:00
|
|
|
const container = make_container();
|
|
|
|
make_scroll_container(container);
|
2020-04-24 12:30:56 +02:00
|
|
|
const sort_container = make_sort_container();
|
2018-04-17 21:29:21 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let cleared;
|
2018-04-17 21:29:21 +02:00
|
|
|
container.html = (html) => {
|
|
|
|
assert.equal(html, '');
|
|
|
|
cleared = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const alice = { name: 'alice', salary: 50 };
|
2019-04-22 01:09:02 +02:00
|
|
|
const bob = { name: 'Bob', salary: 40 };
|
2018-04-17 21:29:21 +02:00
|
|
|
const cal = { name: 'cal', salary: 30 };
|
|
|
|
const dave = { name: 'dave', salary: 25 };
|
2020-04-24 16:13:42 +02:00
|
|
|
const ellen = { name: 'ellen', salary: 95 };
|
2018-04-17 21:29:21 +02:00
|
|
|
|
2020-04-24 16:13:42 +02:00
|
|
|
const list = [bob, ellen, dave, alice, cal];
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
const opts = {
|
2020-04-24 16:13:42 +02:00
|
|
|
name: 'sorting-list',
|
2020-04-24 12:30:56 +02:00
|
|
|
parent_container: sort_container,
|
2018-04-17 21:29:21 +02:00
|
|
|
modifier: (item) => {
|
|
|
|
return div(item.name) + div(item.salary);
|
|
|
|
},
|
2019-12-30 16:13:42 +01:00
|
|
|
filter: {
|
2019-12-30 17:44:24 +01:00
|
|
|
predicate: () => true,
|
2019-12-30 16:13:42 +01:00
|
|
|
},
|
2018-04-17 21:29:21 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
function html_for(people) {
|
js: Convert _.map(a, …) to a.map(…).
And convert the corresponding function expressions to arrow style
while we’re here.
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, {
visitCallExpression(path) {
const { callee, arguments: args } = path.node;
if (
n.MemberExpression.check(callee) &&
!callee.computed &&
n.Identifier.check(callee.object) &&
callee.object.name === "_" &&
n.Identifier.check(callee.property) &&
callee.property.name === "map" &&
args.length === 2 &&
checkExpression(args[0]) &&
checkExpression(args[1])
) {
const [arr, fn] = args;
path.replace(
b.callExpression(b.memberExpression(arr, b.identifier("map")), [
n.FunctionExpression.check(fn) ||
n.ArrowFunctionExpression.check(fn)
? b.arrowFunctionExpression(
fn.params,
n.BlockStatement.check(fn.body) &&
fn.body.body.length === 1 &&
n.ReturnStatement.check(fn.body.body[0])
? fn.body.body[0].argument || b.identifier("undefined")
: fn.body
)
: fn,
])
);
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 02:43:49 +01:00
|
|
|
return people.map(opts.modifier).join('');
|
2018-04-17 21:29:21 +02:00
|
|
|
}
|
|
|
|
|
2020-04-24 12:30:56 +02:00
|
|
|
list_render.create(container, list, opts);
|
2018-04-17 21:29:21 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let button_opts;
|
|
|
|
let button;
|
|
|
|
let expected_html;
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
button_opts = {
|
|
|
|
sort_type: 'alphabetic',
|
|
|
|
prop_name: 'name',
|
|
|
|
list_name: 'my-list',
|
|
|
|
active: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
button = sort_button(button_opts);
|
|
|
|
|
2020-04-24 12:30:56 +02:00
|
|
|
sort_container.f.apply(button);
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
assert(cleared);
|
|
|
|
assert(button.siblings_deactivated);
|
|
|
|
|
2020-04-24 16:13:42 +02:00
|
|
|
expected_html = html_for([
|
|
|
|
alice,
|
|
|
|
bob,
|
|
|
|
cal,
|
|
|
|
dave,
|
|
|
|
ellen,
|
|
|
|
]);
|
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
|
|
|
|
|
|
|
// Hit same button again to reverse the data.
|
|
|
|
cleared = false;
|
|
|
|
sort_container.f.apply(button);
|
|
|
|
assert(cleared);
|
|
|
|
expected_html = html_for([
|
|
|
|
ellen,
|
|
|
|
dave,
|
|
|
|
cal,
|
|
|
|
bob,
|
|
|
|
alice,
|
|
|
|
]);
|
2018-04-17 21:29:21 +02:00
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
2020-04-24 16:13:42 +02:00
|
|
|
assert(button.hasClass('descend'));
|
|
|
|
|
|
|
|
// And then hit a third time to go back to the forward sort.
|
|
|
|
cleared = false;
|
|
|
|
sort_container.f.apply(button);
|
|
|
|
assert(cleared);
|
|
|
|
expected_html = html_for([
|
|
|
|
alice,
|
|
|
|
bob,
|
|
|
|
cal,
|
|
|
|
dave,
|
|
|
|
ellen,
|
|
|
|
]);
|
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
|
|
|
assert(!button.hasClass('descend'));
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
// Now try a numeric sort.
|
|
|
|
button_opts = {
|
|
|
|
sort_type: 'numeric',
|
|
|
|
prop_name: 'salary',
|
|
|
|
list_name: 'my-list',
|
|
|
|
active: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
button = sort_button(button_opts);
|
|
|
|
|
|
|
|
cleared = false;
|
|
|
|
button.siblings_deactivated = false;
|
|
|
|
|
2020-04-24 12:30:56 +02:00
|
|
|
sort_container.f.apply(button);
|
2018-04-17 21:29:21 +02:00
|
|
|
|
|
|
|
assert(cleared);
|
|
|
|
assert(button.siblings_deactivated);
|
|
|
|
|
2020-04-24 16:13:42 +02:00
|
|
|
expected_html = html_for([
|
|
|
|
dave,
|
|
|
|
cal,
|
|
|
|
bob,
|
|
|
|
alice,
|
|
|
|
ellen,
|
|
|
|
]);
|
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
|
|
|
|
|
|
|
// Hit same button again to reverse the numeric sort.
|
|
|
|
cleared = false;
|
|
|
|
sort_container.f.apply(button);
|
|
|
|
assert(cleared);
|
|
|
|
expected_html = html_for([
|
|
|
|
ellen,
|
|
|
|
alice,
|
|
|
|
bob,
|
|
|
|
cal,
|
|
|
|
dave,
|
|
|
|
]);
|
2018-04-17 21:29:21 +02:00
|
|
|
assert.deepEqual(container.appended_data.html(), expected_html);
|
2020-04-24 16:13:42 +02:00
|
|
|
assert(button.hasClass('descend'));
|
2018-05-15 12:40:07 +02:00
|
|
|
});
|
2020-04-24 13:22:32 +02:00
|
|
|
|
2020-04-24 22:23:35 +02:00
|
|
|
run_test('custom sort', () => {
|
|
|
|
const container = make_container();
|
|
|
|
make_scroll_container(container);
|
|
|
|
container.html = () => {};
|
|
|
|
|
|
|
|
const n42 = {x: 6, y: 7};
|
|
|
|
const n43 = {x: 1, y: 43};
|
|
|
|
const n44 = {x: 4, y: 11};
|
|
|
|
|
|
|
|
const list = [n42, n43, n44];
|
|
|
|
|
|
|
|
function sort_by_x(a, b) {
|
|
|
|
return a.x - b.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
function sort_by_product(a, b) {
|
|
|
|
return a.x * a.y - b.x * b.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_render.create(container, list, {
|
|
|
|
name: 'custom-sort-list',
|
|
|
|
modifier: (n) => '(' + n.x + ', ' + n.y + ')',
|
|
|
|
sort_fields: {
|
|
|
|
product: sort_by_product,
|
|
|
|
x_value: sort_by_x,
|
|
|
|
},
|
|
|
|
init_sort: sort_by_product,
|
|
|
|
});
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
container.appended_data.html(),
|
|
|
|
'(6, 7)(1, 43)(4, 11)'
|
|
|
|
);
|
|
|
|
|
|
|
|
const widget = list_render.get('custom-sort-list');
|
|
|
|
|
|
|
|
widget.sort('x_value');
|
|
|
|
assert.deepEqual(
|
|
|
|
container.appended_data.html(),
|
|
|
|
'(1, 43)(4, 11)(6, 7)'
|
|
|
|
);
|
|
|
|
|
|
|
|
// We can sort without registering the function, too.
|
|
|
|
function sort_by_y(a, b) {
|
|
|
|
return a.y - b.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
widget.sort(sort_by_y);
|
|
|
|
assert.deepEqual(
|
|
|
|
container.appended_data.html(),
|
|
|
|
'(6, 7)(4, 11)(1, 43)'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2020-04-24 13:22:32 +02:00
|
|
|
run_test('clear_event_handlers', () => {
|
|
|
|
const container = make_container();
|
|
|
|
const scroll_container = make_scroll_container(container);
|
|
|
|
const sort_container = make_sort_container();
|
|
|
|
const filter_element = make_filter_element();
|
|
|
|
|
|
|
|
// We don't care about actual data for this test.
|
|
|
|
const list = [];
|
|
|
|
container.html = () => {};
|
|
|
|
|
|
|
|
const opts = {
|
|
|
|
name: 'list-we-create-twice',
|
|
|
|
parent_container: sort_container,
|
|
|
|
modifier: () => {},
|
|
|
|
filter: {
|
|
|
|
element: filter_element,
|
|
|
|
predicate: () => true,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create it the first time.
|
|
|
|
list_render.create(container, list, opts);
|
|
|
|
assert.equal(sort_container.cleared, false);
|
|
|
|
assert.equal(scroll_container.cleared, false);
|
|
|
|
assert.equal(filter_element.cleared, false);
|
|
|
|
|
|
|
|
// The second time we'll clear the old events.
|
|
|
|
list_render.create(container, list, opts);
|
|
|
|
assert.equal(sort_container.cleared, true);
|
|
|
|
assert.equal(scroll_container.cleared, true);
|
|
|
|
assert.equal(filter_element.cleared, true);
|
|
|
|
});
|
2020-04-24 15:38:16 +02:00
|
|
|
|
|
|
|
run_test('errors', () => {
|
|
|
|
// We don't care about actual data for this test.
|
|
|
|
const list = 'stub';
|
2020-04-24 15:57:41 +02:00
|
|
|
const container = make_container();
|
|
|
|
make_scroll_container(container);
|
2020-04-24 15:38:16 +02:00
|
|
|
|
|
|
|
blueslip.expect('error', 'Need opts to create widget.');
|
|
|
|
list_render.create(container, list);
|
|
|
|
blueslip.reset();
|
2020-04-24 15:57:41 +02:00
|
|
|
|
|
|
|
blueslip.expect('error', 'Filter predicate is not a function.');
|
|
|
|
list_render.create(container, list, {
|
|
|
|
filter: {
|
|
|
|
predicate: 'wrong type',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
blueslip.reset();
|
|
|
|
|
|
|
|
blueslip.expect('error', 'Filterer and predicate are mutually exclusive.');
|
|
|
|
list_render.create(container, list, {
|
|
|
|
filter: {
|
|
|
|
filterer: () => true,
|
|
|
|
predicate: () => true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
blueslip.reset();
|
|
|
|
|
|
|
|
blueslip.expect('error', 'Filter filterer is not a function (or missing).');
|
|
|
|
list_render.create(container, list, {
|
|
|
|
filter: {
|
|
|
|
},
|
|
|
|
});
|
|
|
|
blueslip.reset();
|
2020-04-24 22:09:17 +02:00
|
|
|
|
|
|
|
container.html = () => {};
|
|
|
|
blueslip.expect('error', 'List item is not a string: 999');
|
|
|
|
list_render.create(container, list, {
|
|
|
|
modifier: () => 999,
|
|
|
|
});
|
|
|
|
blueslip.reset();
|
2020-04-24 15:38:16 +02:00
|
|
|
});
|
2020-04-24 19:49:18 +02:00
|
|
|
|
|
|
|
run_test('sort helpers', () => {
|
|
|
|
/*
|
|
|
|
We mostly test our sorting helpers using the
|
|
|
|
actual widget, but this test gets us a bit
|
|
|
|
more line coverage.
|
|
|
|
*/
|
|
|
|
const alice2 = {name: 'alice', id: 2};
|
|
|
|
const alice10 = {name: 'alice', id: 10};
|
|
|
|
const bob2 = {name: 'bob', id: 2};
|
|
|
|
const bob10 = {name: 'bob', id: 10};
|
|
|
|
|
|
|
|
const alpha_cmp = list_render.alphabetic_sort('name');
|
|
|
|
const num_cmp = list_render.numeric_sort('id');
|
|
|
|
|
|
|
|
assert.equal(alpha_cmp(alice2, alice10), 0);
|
|
|
|
assert.equal(alpha_cmp(alice2, bob2), -1);
|
|
|
|
assert.equal(alpha_cmp(bob2, alice10), 1);
|
|
|
|
assert.equal(num_cmp(alice2, bob2), 0);
|
|
|
|
assert.equal(num_cmp(alice2, bob10), -1);
|
|
|
|
assert.equal(num_cmp(alice10, bob2), 1);
|
|
|
|
});
|