zulip/web/tests/search.test.js

348 lines
14 KiB
JavaScript
Raw Normal View History

"use strict";
const {strict: assert} = require("assert");
const {mock_esm, zrequire} = require("./lib/namespace");
const {run_test, noop} = require("./lib/test");
const $ = require("./lib/zjquery");
const bootstrap_typeahead = mock_esm("../src/bootstrap_typeahead");
const narrow_state = mock_esm("../src/narrow_state");
const search_suggestion = mock_esm("../src/search_suggestion");
const Filter = {};
mock_esm("../src/filter", {
Filter,
});
const search = zrequire("search");
let typeahead_forced_open = false;
run_test("initialize", ({override, override_rewire, mock_template}) => {
const $search_query_box = $("#search_query");
const $searchbox_form = $("#searchbox_form");
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;
});
search_suggestion.max_num_of_search_results = 999;
let terms;
override(bootstrap_typeahead, "Typeahead", (input_element, opts) => {
assert.equal(input_element.$element, $search_query_box);
assert.equal(opts.items, 999);
assert.equal(opts.naturalSearch, true);
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.equal(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&s=50",
},
},
],
},
],
[
"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&s=50",
},
},
],
},
],
[
"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&s=50",
},
},
],
},
],
[
"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.equal(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&amp;s&#x3D;50" />\n <span class="pill-label">\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</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&amp;s&#x3D;50" />\n <span class="pill-label">\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</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&amp;s&#x3D;50" />\n <span class="pill-label">\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</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);
}
{
let is_blurred;
$search_query_box.on("blur", () => {
is_blurred = true;
});
/* Test updater */
const _setup = (search_box_val) => {
is_blurred = false;
$search_query_box.text(search_box_val);
Filter.parse = (search_string) => {
assert.equal(search_string, search_box_val);
return terms;
};
};
terms = [
{
negated: false,
operator: "search",
operand: "ver",
},
];
_setup("ver");
assert.equal(opts.updater("ver"), "ver");
assert.ok(is_blurred);
terms = [
{
negated: false,
operator: "stream",
operand: "Verona",
},
];
_setup("stream:Verona");
assert.equal(opts.updater("stream:Verona"), "stream:Verona");
assert.ok(is_blurred);
search.__Rewire__("is_using_input_method", true);
_setup("stream:Verona");
assert.equal(opts.updater("stream:Verona"), "stream:Verona");
assert.ok(!is_blurred);
$search_query_box.off("blur");
}
return {
lookup() {
typeahead_forced_open = true;
},
};
});
search.initialize({
on_narrow_search(raw_terms, options) {
assert.deepEqual(raw_terms, terms);
assert.deepEqual(options, {trigger: "search"});
},
});
$search_query_box.text("test string");
narrow_state.search_string = () => "ver";
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",
};
let is_blurred;
$search_query_box.off("blur");
$search_query_box.on("blur", () => {
is_blurred = true;
});
const _setup = (search_box_val) => {
is_blurred = false;
$search_query_box.text(search_box_val);
Filter.parse = (search_string) => {
assert.equal(search_string, search_box_val);
return terms;
};
};
terms = [
{
negated: false,
operator: "search",
operand: "",
},
];
_setup("");
ev.key = "a";
/* istanbul ignore next */
$search_query_box.is = () => false;
$searchbox_form.trigger(ev);
assert.ok(!is_blurred);
ev.key = "Enter";
$search_query_box.is = () => false;
$searchbox_form.trigger(ev);
assert.ok(!is_blurred);
override_rewire(search, "exit_search", noop);
ev.key = "Enter";
$search_query_box.is = () => true;
$searchbox_form.trigger(ev);
assert.ok(is_blurred);
_setup("ver");
ev.key = "Enter";
search.__Rewire__("is_using_input_method", true);
$searchbox_form.trigger(ev);
// No change on Enter keyup event when using input tool
assert.ok(!is_blurred);
_setup("ver");
ev.key = "Enter";
$search_query_box.is = () => true;
$searchbox_form.trigger(ev);
assert.ok(is_blurred);
});
run_test("initiate_search", () => {
// open typeahead and select text when navbar is open
// this implicitly expects the code to used the chained
// function calls, which is something to keep in mind if
// this test ever fails unexpectedly.
narrow_state.filter = () => ({is_keyword_search: () => false});
let is_searchbox_text_selected = false;
$("#search_query").on("select", () => {
is_searchbox_text_selected = true;
});
$(".navbar-search.expanded").length = 0;
$("#search_query").text("");
search.initiate_search();
assert.ok(typeahead_forced_open);
assert.ok(is_searchbox_text_selected);
// test that we append space for user convenience
assert.equal($("#search_query").text(), "ver ");
});