zulip/web/tests/search.test.js

380 lines
14 KiB
JavaScript
Raw Normal View History

"use strict";
const assert = require("node:assert/strict");
const {mock_esm, set_global, zrequire} = require("./lib/namespace.js");
const {run_test, noop} = require("./lib/test.js");
const $ = require("./lib/zjquery.js");
const bootstrap_typeahead = mock_esm("../src/bootstrap_typeahead");
const search_suggestion = mock_esm("../src/search_suggestion");
const search = zrequire("search");
const search_pill = zrequire("search_pill");
const stream_data = zrequire("stream_data");
function stub_pills() {
const $pill_container = $("#searchbox-input-container.pill-container");
const $pill_input = $.create("pill_input");
$pill_container.set_find_results(".input", $pill_input);
$pill_input.before = noop;
}
set_global("getSelection", () => ({
modify: noop,
}));
let typeahead_forced_open = false;
const verona = {
subscribed: true,
color: "blue",
name: "Verona",
stream_id: 1,
};
stream_data.add_sub(verona);
run_test("initialize", ({override, override_rewire, mock_template}) => {
const $search_query_box = $("#search_query");
const $searchbox_form = $("#searchbox_form");
stub_pills();
mock_template("search_list_item.hbs", true, (data, html) => {
assert.equal(typeof data.description_html, "string");
if (data.is_people) {
for (const user of data.users) {
assert.equal(typeof user.user_pill_context.id, "number");
assert.equal(typeof user.user_pill_context.display_value, "string");
assert.equal(typeof user.user_pill_context.has_image, "boolean");
assert.equal(typeof user.user_pill_context.img_src, "string");
}
}
return html;
});
let expected_pill_display_value = "";
let input_pill_displayed = false;
mock_template("input_pill.hbs", true, (data, html) => {
assert.equal(data.display_value, expected_pill_display_value);
input_pill_displayed = true;
return html;
});
search_suggestion.max_num_of_search_results = 999;
let terms;
function mock_pill_removes(widget) {
const pills = widget._get_pills_for_testing();
for (const pill of pills) {
pill.$element.remove = noop;
}
}
override(bootstrap_typeahead, "Typeahead", (input_element, opts) => {
assert.equal(input_element.$element, $search_query_box);
assert.equal(opts.items, 999);
assert.equal(opts.helpOnEmptyStrings, true);
assert.equal(opts.matcher(), true);
{
const search_suggestions = {
lookup_table: new Map([
[
"stream:Verona",
{
description_html: "Stream <strong>Ver</strong>ona",
search_string: "stream:Verona",
},
],
[
"ver",
{
description_html: "Search for ver",
search_string: "ver",
},
],
]),
strings: ["ver", "stream:Verona"],
};
/* Test source */
search suggestions: De-duplicate legacy search codepath. This merges the `exports.get_search_result_legacy` and `exports.get_search_result` function. The key differences between the two code paths are as follows: * We only want to generate suggestions for the queries which the user is typing or can edit. For the legacy version, suggestions are displayed for the entire search string in the searchbox. (`all_operators`) For the pills enabled version, suggestions are displayed only for the input which hasn't been converted to pills. (`query_operators`) `all_operators` = `base_query_operators` + " " + `query_operators`. trim is added at the end just to handle the legacy case where we pass the `base_query` as ''. * It is not possible to detect whether the user wants to continue typing in the legacy version. However if the the searchbox is still focused even after pill creation we can assume the user still wants to continue typing. To handle this we push an empty term as the `last` operator. This is possible since the previous queries have been completely entered as evident from it's generated pill. * When using the legacy version, `search_operators` are the same as `all_operators`, as mentioned in point 1. In the pills enabled version we perform most of the computations from the `query_operators`, but we do require all `all_operators`, only for filtering the last query's suggestion. * And there is just one block unique to the legacy search system. More details are mentioned in the comments of that block. We also refactor both the search suggestions node tests, mainly to make them similar and easier to detect differences when we switch over to the new version.
2020-06-01 15:00:42 +02:00
search_suggestion.get_suggestions = () => search_suggestions;
const expected_source_value = search_suggestions.strings;
const source = opts.source("ver");
assert.deepStrictEqual(source, expected_source_value);
/* Test highlighter */
let expected_value = `<div class="search_list_item">\n <span>Search for ver</span>\n</div>\n`;
assert.equal(opts.highlighter_html(source[0]), expected_value);
expected_value = `<div class="search_list_item">\n <span>Stream <strong>Ver</strong>ona</span>\n</div>\n`;
assert.equal(opts.highlighter_html(source[1]), expected_value);
/* Test sorter */
assert.equal(opts.sorter(search_suggestions.strings), search_suggestions.strings);
}
{
const search_suggestions = {
lookup_table: new Map([
[
"dm-including:zo",
{
description_html: "group direct messages including",
is_people: true,
search_string: "dm-including:user7@zulipdev.com",
users: [
{
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1",
},
},
],
},
],
[
"dm:zo",
{
description_html: "direct messages with",
is_people: true,
search_string: "dm:user7@zulipdev.com",
users: [
{
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1",
},
},
],
},
],
[
"sender:zo",
{
description_html: "sent by",
is_people: true,
search_string: "sender:user7@zulipdev.com",
users: [
{
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1",
},
},
],
},
],
[
"zo",
{
description_html: "Search for zo",
search_string: "zo",
},
],
]),
strings: ["zo", "sender:zo", "dm:zo", "dm-including:zo"],
};
/* Test source */
search_suggestion.get_suggestions = () => search_suggestions;
const expected_source_value = search_suggestions.strings;
const source = opts.source("zo");
assert.deepStrictEqual(source, expected_source_value);
/* Test highlighter */
let expected_value = `<div class="search_list_item">\n <span>Search for zo</span>\n</div>\n`;
assert.equal(opts.highlighter_html(source[0]), expected_value);
expected_value = `<div class="search_list_item">\n <span>sent by</span>\n <span class="pill-container">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1" />\n <span class="pill-label">\n <span class="pill-value">\n &lt;strong&gt;Zo&lt;/strong&gt;e\n </span></span>\n <div class="exit">\n <a role="button" class="zulip-icon zulip-icon-close pill-close-button"></a>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter_html(source[1]), expected_value);
expected_value = `<div class="search_list_item">\n <span>direct messages with</span>\n <span class="pill-container">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1" />\n <span class="pill-label">\n <span class="pill-value">\n &lt;strong&gt;Zo&lt;/strong&gt;e\n </span></span>\n <div class="exit">\n <a role="button" class="zulip-icon zulip-icon-close pill-close-button"></a>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter_html(source[2]), expected_value);
expected_value = `<div class="search_list_item">\n <span>group direct messages including</span>\n <span class="pill-container">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1" />\n <span class="pill-label">\n <span class="pill-value">\n &lt;strong&gt;Zo&lt;/strong&gt;e\n </span></span>\n <div class="exit">\n <a role="button" class="zulip-icon zulip-icon-close pill-close-button"></a>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter_html(source[3]), expected_value);
/* Test sorter */
assert.equal(opts.sorter(search_suggestions.strings), search_suggestions.strings);
}
{
/* Test updater */
const _setup = (terms) => {
const pills = search.search_pill_widget._get_pills_for_testing();
for (const pill of pills) {
pill.$element.remove = noop;
}
search_pill.set_search_bar_contents(
terms,
search.search_pill_widget,
$search_query_box.text,
);
};
terms = [
{
negated: false,
operator: "search",
operand: "ver",
},
];
expected_pill_display_value = null;
_setup(terms);
input_pill_displayed = false;
mock_pill_removes(search.search_pill_widget);
$(".navbar-search.expanded").length = 1;
assert.equal(opts.updater("ver"), "ver");
assert.ok(!input_pill_displayed);
const verona_stream_id = verona.stream_id.toString();
terms = [
{
negated: false,
operator: "channel",
operand: verona_stream_id,
},
];
expected_pill_display_value = "channel: Verona";
_setup(terms);
input_pill_displayed = false;
mock_pill_removes(search.search_pill_widget);
assert.equal(opts.updater(`channel:${verona_stream_id}`), "");
assert.ok(input_pill_displayed);
search.rewire_is_using_input_method(true);
_setup(terms);
input_pill_displayed = false;
mock_pill_removes(search.search_pill_widget);
assert.equal(opts.updater(`channel:${verona_stream_id}`), "");
assert.ok(input_pill_displayed);
}
return {
lookup() {
typeahead_forced_open = true;
},
};
});
search.initialize({
on_narrow_search() {},
});
$search_query_box.text("test string");
search.rewire_is_using_input_method(false);
$searchbox_form.trigger("compositionend");
assert.ok(search.is_using_input_method);
const keydown = $searchbox_form.get_on_handler("keydown");
let default_prevented = false;
let ev = {
type: "keydown",
which: 15,
preventDefault() {
default_prevented = true;
},
};
$search_query_box.is = () => false;
assert.equal(keydown(ev), undefined);
assert.ok(!default_prevented);
ev.key = "Enter";
assert.equal(keydown(ev), undefined);
assert.ok(!default_prevented);
ev.key = "Enter";
$search_query_box.is = () => true;
assert.equal(keydown(ev), undefined);
assert.ok(default_prevented);
ev = {
type: "keyup",
};
const _setup = (terms) => {
const pills = search.search_pill_widget._get_pills_for_testing();
for (const pill of pills) {
pill.$element.remove = noop;
}
search_pill.set_search_bar_contents(
terms,
search.search_pill_widget,
$search_query_box.text,
);
};
terms = [
{
negated: false,
operator: "search",
operand: "",
},
];
_setup(terms);
ev.key = "a";
/* istanbul ignore next */
$search_query_box.is = () => false;
$searchbox_form.trigger(ev);
let search_exited = false;
override_rewire(search, "exit_search", () => {
search_exited = true;
});
ev.key = "Enter";
$search_query_box.is = () => false;
$searchbox_form.trigger(ev);
assert.ok(!search_exited);
ev.key = "Enter";
$search_query_box.is = () => true;
$searchbox_form.trigger(ev);
assert.ok(search_exited);
let is_blurred = false;
$search_query_box.on("blur", () => {
is_blurred = true;
});
terms = [
{
negated: false,
operator: "search",
operand: "ver",
},
];
expected_pill_display_value = "ver";
_setup(terms);
ev.key = "Enter";
search.rewire_is_using_input_method(true);
$searchbox_form.trigger(ev);
// No change on first Enter keyup event
assert.ok(!is_blurred);
$searchbox_form.trigger(ev);
assert.ok(is_blurred);
});
run_test("initiate_search", ({override_rewire}) => {
let search_bar_opened = false;
override_rewire(search, "open_search_bar_and_close_narrow_description", () => {
search_bar_opened = true;
});
$(".navbar-search.expanded").length = 0;
$("#search_query").text("");
search.initiate_search();
assert.ok(typeahead_forced_open);
assert.ok(search_bar_opened);
assert.equal($("#search_query").text(), "");
});