<commit_before>"use strict";

zrequire("list_render");

// 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");
function Element() {
    return {};
}
set_global("Element", Element);
set_global("ui", {});

// 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 {
        replace: (regex, string) => {
            arg = arg.replace(regex, string);
        },
        html: () => arg,
    };
});

// 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() {
    const container = {};

    container.length = () => 1;
    container.is = () => false;
    container.css = (prop) => {
        assert.equal(prop, "max-height");
        return "none";
    };

    // 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() {
    const scroll_container = {};

    scroll_container.cleared = false;

    // Capture the scroll callback so we can call it in
    // our tests.
    scroll_container.on = (ev, f) => {
        assert.equal(ev, "scroll.list_widget_container");
        scroll_container.call_scroll = () => {
            f.call(scroll_container);
        };
    };

    scroll_container.off = (ev) => {
        assert.equal(ev, "scroll.list_widget_container");
        scroll_container.cleared = true;
    };

    return scroll_container;
}

function make_sort_container() {
    const sort_container = {};

    sort_container.cleared = false;

    sort_container.on = (ev, sel, f) => {
        assert.equal(ev, "click.list_widget_sort");
        assert.equal(sel, "[data-sort]");
        sort_container.f = f;
    };

    sort_container.off = (ev) => {
        assert.equal(ev, "click.list_widget_sort");
        sort_container.cleared = true;
    };

    return sort_container;
}

function make_filter_element() {
    const element = {};

    element.cleared = false;

    element.on = (ev, f) => {
        assert.equal(ev, "input.list_widget_filter");
        element.f = f;
    };

    element.off = (ev) => {
        assert.equal(ev, "input.list_widget_filter");
        element.cleared = true;
    };

    return element;
}

function make_search_input() {
    const $element = {};

    // Allow ourselves to be wrapped by $(...) and
    // return ourselves.
    $element.to_jquery = () => $element;

    $element.on = (event_name, f) => {
        assert.equal(event_name, "input.list_widget_filter");
        $element.simulate_input_event = () => {
            const elem = {
                value: $element.val(),
            };
            f.call(elem);
        };
    };

    return $element;
}

function div(item) {
    return "<div>" + item + "</div>";
}

run_test("scrolling", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();

    const items = [];

    let get_scroll_element_called = false;
    ui.get_scroll_element = (element) => {
        get_scroll_element_called = true;
        return element;
    };

    for (let i = 0; i < 200; i += 1) {
        items.push("item " + i);
    }

    const opts = {
        modifier: (item) => item,
        simplebar_container: scroll_container,
    };

    container.html = (html) => {
        assert.equal(html, "");
    };
    list_render.create(container, items, opts);

    assert.deepEqual(container.appended_data.html(), items.slice(0, 80).join(""));
    assert.equal(get_scroll_element_called, true);

    // Set up our fake geometry so it forces a scroll action.
    scroll_container.scrollTop = 180;
    scroll_container.clientHeight = 100;
    scroll_container.scrollHeight = 260;

    // Scrolling gets the next two elements from the list into
    // our widget.
    scroll_container.call_scroll();
    assert.deepEqual(container.appended_data.html(), items.slice(80, 100).join(""));
});

run_test("filtering", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();

    const search_input = make_search_input();

    const list = ["apple", "banana", "carrot", "dog", "egg", "fence", "grape"];
    const opts = {
        filter: {
            element: search_input,
            predicate: (item, value) => item.includes(value),
        },
        modifier: (item) => div(item),
        simplebar_container: scroll_container,
    };

    container.html = (html) => {
        assert.equal(html, "");
    };
    const widget = list_render.create(container, list, opts);

    let expected_html =
        "<div>apple</div>" +
        "<div>banana</div>" +
        "<div>carrot</div>" +
        "<div>dog</div>" +
        "<div>egg</div>" +
        "<div>fence</div>" +
        "<div>grape</div>";

    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"];

    widget.replace_list_data(new_data);
    expected_html = "<div>greta</div>" + "<div>gary</div>" + "<div>giraffe</div>";
    assert.deepEqual(container.appended_data.html(), expected_html);
});

run_test("no filtering", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();
    container.html = () => {};

    // Opts does not require a filter key.
    const opts = {
        modifier: (item) => div(item),
        simplebar_container: scroll_container,
    };
    const widget = list_render.create(container, ["apple", "banana"], opts);
    widget.render();

    const expected_html = "<div>apple</div>" + "<div>banana</div>";
    assert.deepEqual(container.appended_data.html(), expected_html);
});

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) {
            case "sort":
                return opts.sort_type;
            case "sort-prop":
                return opts.prop_name;
            default:
                throw Error("unknown selector: " + sel);
        }
    }

    function lookup(sel, value) {
        return (selector) => {
            assert.equal(sel, selector);
            return value;
        };
    }

    const classList = new Set();

    const button = {
        data,
        closest: lookup(".progressive-table-wrapper", {
            data: lookup("list-render", opts.list_name),
        }),
        addClass: (cls) => {
            classList.add(cls);
        },
        hasClass: (cls) => classList.has(cls),
        removeClass: (cls) => {
            classList.delete(cls);
        },
        siblings: lookup(".active", {
            removeClass: (cls) => {
                assert.equal(cls, "active");
                button.siblings_deactivated = true;
            },
        }),
        siblings_deactivated: false,
        to_jquery: () => button,
    };

    return button;
}

run_test("wire up filter element", () => {
    const lst = ["alice", "JESSE", "moses", "scott", "Sean", "Xavier"];

    const container = make_container();
    const scroll_container = make_scroll_container();
    const filter_element = make_filter_element();

    // We don't care about what gets drawn initially.
    container.html = () => {};

    const opts = {
        filter: {
            filterer: (list, value) => list.filter((item) => item.toLowerCase().includes(value)),
            element: filter_element,
        },
        modifier: (s) => "(" + s + ")",
        simplebar_container: scroll_container,
    };

    list_render.create(container, lst, opts);
    filter_element.f.apply({value: "se"});
    assert.equal(container.appended_data.html(), "(JESSE)(moses)(Sean)");
});

run_test("sorting", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();
    const sort_container = make_sort_container();

    let cleared;
    container.html = (html) => {
        assert.equal(html, "");
        cleared = true;
    };

    const alice = {name: "alice", salary: 50};
    const bob = {name: "Bob", salary: 40};
    const cal = {name: "cal", salary: 30};
    const dave = {name: "dave", salary: 25};
    const ellen = {name: "ellen", salary: 95};

    const list = [bob, ellen, dave, alice, cal];

    const opts = {
        name: "sorting-list",
        parent_container: sort_container,
        modifier: (item) => div(item.name) + div(item.salary),
        filter: {
            predicate: () => true,
        },
        simplebar_container: scroll_container,
    };

    function html_for(people) {
        return people.map(opts.modifier).join("");
    }

    list_render.create(container, list, opts);

    let button_opts;
    let button;
    let expected_html;

    button_opts = {
        sort_type: "alphabetic",
        prop_name: "name",
        list_name: "my-list",
        active: false,
    };

    button = sort_button(button_opts);

    sort_container.f.apply(button);

    assert(cleared);
    assert(button.siblings_deactivated);

    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]);
    assert.deepEqual(container.appended_data.html(), expected_html);
    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"));

    // 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;

    sort_container.f.apply(button);

    assert(cleared);
    assert(button.siblings_deactivated);

    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]);
    assert.deepEqual(container.appended_data.html(), expected_html);
    assert(button.hasClass("descend"));
});

run_test("custom sort", () => {
    const container = make_container();
    const scroll_container = make_scroll_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,
        simplebar_container: scroll_container,
    });

    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)");
});

run_test("clear_event_handlers", () => {
    const container = make_container();
    const scroll_container = make_scroll_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,
        },
        simplebar_container: scroll_container,
    };

    // 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);
});

run_test("errors", () => {
    // We don't care about actual data for this test.
    const list = "stub";
    const container = make_container();
    const scroll_container = make_scroll_container();

    blueslip.expect("error", "Need opts to create widget.");
    list_render.create(container, list);
    blueslip.reset();

    blueslip.expect("error", "simplebar_container is missing.");
    list_render.create(container, list, {
        modifier: "hello world",
    });
    blueslip.reset();

    blueslip.expect("error", "get_item should be a function");
    list_render.create(container, list, {
        get_item: "not a function",
        simplebar_container: scroll_container,
    });
    blueslip.reset();

    blueslip.expect("error", "Filter predicate is not a function.");
    list_render.create(container, list, {
        filter: {
            predicate: "wrong type",
        },
        simplebar_container: scroll_container,
    });
    blueslip.reset();
<commit_msg>node_tests: list_render: Fix init_sort argument.<commit_after>"use strict";

zrequire("list_render");

// 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");
function Element() {
    return {};
}
set_global("Element", Element);
set_global("ui", {});

// 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 {
        replace: (regex, string) => {
            arg = arg.replace(regex, string);
        },
        html: () => arg,
    };
});

// 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() {
    const container = {};

    container.length = () => 1;
    container.is = () => false;
    container.css = (prop) => {
        assert.equal(prop, "max-height");
        return "none";
    };

    // 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() {
    const scroll_container = {};

    scroll_container.cleared = false;

    // Capture the scroll callback so we can call it in
    // our tests.
    scroll_container.on = (ev, f) => {
        assert.equal(ev, "scroll.list_widget_container");
        scroll_container.call_scroll = () => {
            f.call(scroll_container);
        };
    };

    scroll_container.off = (ev) => {
        assert.equal(ev, "scroll.list_widget_container");
        scroll_container.cleared = true;
    };

    return scroll_container;
}

function make_sort_container() {
    const sort_container = {};

    sort_container.cleared = false;

    sort_container.on = (ev, sel, f) => {
        assert.equal(ev, "click.list_widget_sort");
        assert.equal(sel, "[data-sort]");
        sort_container.f = f;
    };

    sort_container.off = (ev) => {
        assert.equal(ev, "click.list_widget_sort");
        sort_container.cleared = true;
    };

    return sort_container;
}

function make_filter_element() {
    const element = {};

    element.cleared = false;

    element.on = (ev, f) => {
        assert.equal(ev, "input.list_widget_filter");
        element.f = f;
    };

    element.off = (ev) => {
        assert.equal(ev, "input.list_widget_filter");
        element.cleared = true;
    };

    return element;
}

function make_search_input() {
    const $element = {};

    // Allow ourselves to be wrapped by $(...) and
    // return ourselves.
    $element.to_jquery = () => $element;

    $element.on = (event_name, f) => {
        assert.equal(event_name, "input.list_widget_filter");
        $element.simulate_input_event = () => {
            const elem = {
                value: $element.val(),
            };
            f.call(elem);
        };
    };

    return $element;
}

function div(item) {
    return "<div>" + item + "</div>";
}

run_test("scrolling", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();

    const items = [];

    let get_scroll_element_called = false;
    ui.get_scroll_element = (element) => {
        get_scroll_element_called = true;
        return element;
    };

    for (let i = 0; i < 200; i += 1) {
        items.push("item " + i);
    }

    const opts = {
        modifier: (item) => item,
        simplebar_container: scroll_container,
    };

    container.html = (html) => {
        assert.equal(html, "");
    };
    list_render.create(container, items, opts);

    assert.deepEqual(container.appended_data.html(), items.slice(0, 80).join(""));
    assert.equal(get_scroll_element_called, true);

    // Set up our fake geometry so it forces a scroll action.
    scroll_container.scrollTop = 180;
    scroll_container.clientHeight = 100;
    scroll_container.scrollHeight = 260;

    // Scrolling gets the next two elements from the list into
    // our widget.
    scroll_container.call_scroll();
    assert.deepEqual(container.appended_data.html(), items.slice(80, 100).join(""));
});

run_test("filtering", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();

    const search_input = make_search_input();

    const list = ["apple", "banana", "carrot", "dog", "egg", "fence", "grape"];
    const opts = {
        filter: {
            element: search_input,
            predicate: (item, value) => item.includes(value),
        },
        modifier: (item) => div(item),
        simplebar_container: scroll_container,
    };

    container.html = (html) => {
        assert.equal(html, "");
    };
    const widget = list_render.create(container, list, opts);

    let expected_html =
        "<div>apple</div>" +
        "<div>banana</div>" +
        "<div>carrot</div>" +
        "<div>dog</div>" +
        "<div>egg</div>" +
        "<div>fence</div>" +
        "<div>grape</div>";

    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"];

    widget.replace_list_data(new_data);
    expected_html = "<div>greta</div>" + "<div>gary</div>" + "<div>giraffe</div>";
    assert.deepEqual(container.appended_data.html(), expected_html);
});

run_test("no filtering", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();
    container.html = () => {};

    // Opts does not require a filter key.
    const opts = {
        modifier: (item) => div(item),
        simplebar_container: scroll_container,
    };
    const widget = list_render.create(container, ["apple", "banana"], opts);
    widget.render();

    const expected_html = "<div>apple</div>" + "<div>banana</div>";
    assert.deepEqual(container.appended_data.html(), expected_html);
});

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) {
            case "sort":
                return opts.sort_type;
            case "sort-prop":
                return opts.prop_name;
            default:
                throw Error("unknown selector: " + sel);
        }
    }

    function lookup(sel, value) {
        return (selector) => {
            assert.equal(sel, selector);
            return value;
        };
    }

    const classList = new Set();

    const button = {
        data,
        closest: lookup(".progressive-table-wrapper", {
            data: lookup("list-render", opts.list_name),
        }),
        addClass: (cls) => {
            classList.add(cls);
        },
        hasClass: (cls) => classList.has(cls),
        removeClass: (cls) => {
            classList.delete(cls);
        },
        siblings: lookup(".active", {
            removeClass: (cls) => {
                assert.equal(cls, "active");
                button.siblings_deactivated = true;
            },
        }),
        siblings_deactivated: false,
        to_jquery: () => button,
    };

    return button;
}

run_test("wire up filter element", () => {
    const lst = ["alice", "JESSE", "moses", "scott", "Sean", "Xavier"];

    const container = make_container();
    const scroll_container = make_scroll_container();
    const filter_element = make_filter_element();

    // We don't care about what gets drawn initially.
    container.html = () => {};

    const opts = {
        filter: {
            filterer: (list, value) => list.filter((item) => item.toLowerCase().includes(value)),
            element: filter_element,
        },
        modifier: (s) => "(" + s + ")",
        simplebar_container: scroll_container,
    };

    list_render.create(container, lst, opts);
    filter_element.f.apply({value: "se"});
    assert.equal(container.appended_data.html(), "(JESSE)(moses)(Sean)");
});

run_test("sorting", () => {
    const container = make_container();
    const scroll_container = make_scroll_container();
    const sort_container = make_sort_container();

    let cleared;
    container.html = (html) => {
        assert.equal(html, "");
        cleared = true;
    };

    const alice = {name: "alice", salary: 50};
    const bob = {name: "Bob", salary: 40};
    const cal = {name: "cal", salary: 30};
    const dave = {name: "dave", salary: 25};
    const ellen = {name: "ellen", salary: 95};

    const list = [bob, ellen, dave, alice, cal];

    const opts = {
        name: "sorting-list",
        parent_container: sort_container,
        modifier: (item) => div(item.name) + div(item.salary),
        filter: {
            predicate: () => true,
        },
        simplebar_container: scroll_container,
    };

    function html_for(people) {
        return people.map(opts.modifier).join("");
    }

    list_render.create(container, list, opts);

    let button_opts;
    let button;
    let expected_html;

    button_opts = {
        sort_type: "alphabetic",
        prop_name: "name",
        list_name: "my-list",
        active: false,
    };

    button = sort_button(button_opts);

    sort_container.f.apply(button);

    assert(cleared);
    assert(button.siblings_deactivated);

    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]);
    assert.deepEqual(container.appended_data.html(), expected_html);
    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"));

    // 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;

    sort_container.f.apply(button);

    assert(cleared);
    assert(button.siblings_deactivated);

    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]);
    assert.deepEqual(container.appended_data.html(), expected_html);
    assert(button.hasClass("descend"));
});

run_test("custom sort", () => {
    const container = make_container();
    const scroll_container = make_scroll_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],
        simplebar_container: scroll_container,
    });

    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)");
});

run_test("clear_event_handlers", () => {
    const container = make_container();
    const scroll_container = make_scroll_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,
        },
        simplebar_container: scroll_container,
    };

    // 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);
});

run_test("errors", () => {
    // We don't care about actual data for this test.
    const list = ["stub"];
    const container = make_container();
    const scroll_container = make_scroll_container();

    blueslip.expect("error", "Need opts to create widget.");
    list_render.create(container, list);
    blueslip.reset();

    blueslip.expect("error", "simplebar_container is missing.");
    list_render.create(container, list, {
        modifier: "hello world",
    });
    blueslip.reset();

    blueslip.expect("error", "get_item should be a function");
    list_render.create(container, list, {
        get_item: "not a function",
        simplebar_container: scroll_container,
    });
    blueslip.reset();

    blueslip.expect("error", "Filter predicate is not a function.");
    list_render.create(container, list, {
        filter: {
            predicate: "wrong type",
        },
        simplebar_container: scroll_container,
    });
    blueslip.reset();

{'fraction_non_alphanumeric': 0.14290430132373816, 'fraction_numerical': 0.00508368269897336, 'mean_word_length': 2.6288931480594155, 'pattern_counts': {'":': 4, '<': 69, '<?xml version=': 0, '>': 171, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6633241', 'n_tokens_mistral': 9490, 'n_tokens_neox': 9173, 'n_words': 2198}