search: Create a template for search descriptions.

This commit is contained in:
evykassirer 2023-06-29 19:45:08 -04:00 committed by Tim Abbott
parent 6346110b56
commit 8f5305a4ce
4 changed files with 119 additions and 51 deletions

View File

@ -1,7 +1,7 @@
import Handlebars from "handlebars/runtime";
import _ from "lodash";
import * as resolved_topic from "../shared/src/resolved_topic";
import render_search_description from "../templates/search_description.hbs";
import * as hash_util from "./hash_util";
import {$t} from "./i18n";
@ -1030,34 +1030,14 @@ export class Filter {
return "";
}
static describe_is_operator(operator) {
const verb = operator.negated ? "exclude " : "";
const operand = operator.operand;
switch (operand) {
case "starred":
case "alerted":
case "unread":
return verb + operand + " messages";
case "mentioned":
return verb + "@-mentions";
case "dm":
case "private":
return verb + "direct messages";
case "resolved":
return verb + "topics marked as resolved";
}
return "invalid " + operand + " operand for is operator";
}
// Convert a list of operators to a human-readable description.
static describe_unescaped(operators) {
if (operators.length === 0) {
return "all messages";
}
static parts_for_describe(operators) {
const parts = [];
let parts = [];
if (operators.length === 0) {
parts.push({type: "plain_text", content: "all messages"});
return parts;
}
if (operators.length >= 2) {
const is = (term, expected) => term.operator === expected && !term.negated;
@ -1065,8 +1045,11 @@ export class Filter {
if (is(operators[0], "stream") && is(operators[1], "topic")) {
const stream = operators[0].operand;
const topic = operators[1].operand;
const part = "stream " + stream + " > " + topic;
parts = [part];
parts.push({
type: "stream_topic",
stream,
topic,
});
operators = operators.slice(2);
}
}
@ -1075,7 +1058,12 @@ export class Filter {
const operand = elem.operand;
const canonicalized_operator = Filter.canonicalize_operator(elem.operator);
if (canonicalized_operator === "is") {
return Filter.describe_is_operator(elem);
const verb = elem.negated ? "exclude " : "";
return {
type: "is_operator",
verb,
operand,
};
}
if (canonicalized_operator === "has") {
// search_suggestion.get_suggestions takes care that this message will
@ -1089,7 +1077,10 @@ export class Filter {
"attachments",
];
if (!valid_has_operands.includes(operand)) {
return "invalid " + operand + " operand for has operator";
return {
type: "invalid_has",
operand,
};
}
}
const prefix_for_operator = Filter.operator_to_prefix(
@ -1097,15 +1088,24 @@ export class Filter {
elem.negated,
);
if (prefix_for_operator !== "") {
return prefix_for_operator + " " + operand;
return {
type: "prefix_for_operator",
prefix_for_operator,
operand,
};
}
return "unknown operator";
return {
type: "plain_text",
content: "unknown operator",
};
});
return [...parts, ...more_parts].join(", ");
return [...parts, ...more_parts];
}
static search_description_as_html(operators) {
return Handlebars.Utils.escapeExpression(Filter.describe_unescaped(operators));
return render_search_description({
parts: Filter.parts_for_describe(operators),
});
}
static is_spectator_compatible(ops) {

View File

@ -0,0 +1,42 @@
{{~#each parts ~}}
{{#if (eq this.type "plain_text")~}}
{{~this.content~}}
{{else if (eq this.type "stream_topic")}}
{{~!-- squash whitespace --~}}
stream {{this.stream}} > {{this.topic}}
{{~!-- squash whitespace --~}}
{{else if (eq this.type "invalid_has")}}
{{~!-- squash whitespace --~}}
invalid {{this.operand}} operand for has operator
{{~!-- squash whitespace --~}}
{{else if (eq this.type "prefix_for_operator")}}
{{~!-- squash whitespace --~}}
{{this.prefix_for_operator}} {{this.operand}}
{{~!-- squash whitespace --~}}
{{else if (eq this.type "is_operator")}}
{{#if (eq this.operand "mentioned")}}
{{~!-- squash whitespace --~}}
{{this.verb}}@-mentions
{{~!-- squash whitespace --~}}
{{else if (or (eq this.operand "starred") (eq this.operand "alerted") (eq this.operand "unread"))}}
{{~!-- squash whitespace --~}}
{{this.verb}}{{this.operand}} messages
{{~!-- squash whitespace --~}}
{{else if (or (eq this.operand "dm") (eq this.operand "private"))}}
{{~!-- squash whitespace --~}}
{{this.verb}}direct messages
{{~!-- squash whitespace --~}}
{{else if (eq this.operand "resolved")}}
{{~!-- squash whitespace --~}}
{{this.verb}}topics marked as resolved
{{~!-- squash whitespace --~}}
{{else}}
{{~!-- squash whitespace --~}}
invalid {{this.operand}} operand for is operator
{{~!-- squash whitespace --~}}
{{~/if~}}
{{~/if~}}
{{~#if (not @last)~}}, {{/if~}}
{{~/each~}}

View File

@ -1156,9 +1156,10 @@ test("unparse", () => {
assert.deepEqual(Filter.unparse(operators), string);
});
test("describe", () => {
test("describe", ({mock_template}) => {
let narrow;
let string;
mock_template("search_description.hbs", true, (_data, html) => html);
narrow = [{operator: "streams", operand: "public"}];
string = "streams public";
@ -1186,7 +1187,7 @@ test("describe", () => {
{operator: "stream", operand: "devel"},
{operator: "topic", operand: "JS"},
];
string = "stream devel > JS";
string = "stream devel > JS";
assert.equal(Filter.search_description_as_html(narrow), string);
narrow = [

View File

@ -79,7 +79,9 @@ function test(label, f) {
});
}
test("basic_get_suggestions", ({override}) => {
test("basic_get_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (data, html) => html);
const query = "fred";
override(narrow_state, "stream", () => "office");
@ -99,7 +101,9 @@ test("basic_get_suggestions_for_spectator", () => {
page_params.is_spectator = false;
});
test("subset_suggestions", () => {
test("subset_suggestions", ({mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
const query = "stream:Denmark topic:Hamlet shakespeare";
const suggestions = get_suggestions(query);
@ -113,7 +117,9 @@ test("subset_suggestions", () => {
assert.deepEqual(suggestions.strings, expected);
});
test("dm_suggestions", ({override}) => {
test("dm_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
let query = "is:dm";
let suggestions = get_suggestions(query);
let expected = [
@ -248,7 +254,9 @@ test("dm_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("group_suggestions", () => {
test("group_suggestions", ({mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
// Entering a comma in a "dm:" query should immediately
// generate suggestions for the next person.
let query = "dm:bob@zulip.com,";
@ -446,7 +454,9 @@ test("empty_query_suggestions", () => {
assert.equal(describe("has:attachment"), "Messages that contain attachments");
});
test("has_suggestions", ({override}) => {
test("has_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
// Checks that category wise suggestions are displayed instead of a single
// default suggestion when suggesting `has` operator.
let query = "h";
@ -506,7 +516,9 @@ test("has_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("check_is_suggestions", ({override}) => {
test("check_is_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true});
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
override(narrow_state, "stream", () => {});
@ -587,7 +599,9 @@ test("check_is_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("sent_by_me_suggestions", ({override}) => {
test("sent_by_me_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
override(narrow_state, "stream", () => {});
let query = "";
@ -659,7 +673,8 @@ test("sent_by_me_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("topic_suggestions", ({override}) => {
test("topic_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
let suggestions;
let expected;
@ -703,7 +718,7 @@ test("topic_suggestions", ({override}) => {
return suggestions.lookup_table.get(q).description_html;
}
assert.equal(describe("te"), "Search for te");
assert.equal(describe("stream:office topic:team"), "Stream office > team");
assert.equal(describe("stream:office topic:team"), "Stream office > team");
suggestions = get_suggestions("topic:staplers stream:office");
expected = ["topic:staplers stream:office", "topic:staplers"];
@ -783,7 +798,9 @@ test("topic_suggestions (limits)", () => {
assert_result("z", []);
});
test("whitespace_glitch", ({override}) => {
test("whitespace_glitch", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
const query = "stream:office "; // note trailing space
override(stream_topic_history_util, "get_server_history", () => {});
@ -796,7 +813,9 @@ test("whitespace_glitch", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("stream_completion", ({override}) => {
test("stream_completion", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});
@ -818,7 +837,9 @@ test("stream_completion", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("people_suggestions", ({override}) => {
test("people_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
let query = "te";
override(narrow_state, "stream", () => {});
@ -920,7 +941,9 @@ test("people_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("operator_suggestions", ({override}) => {
test("operator_suggestions", ({override, mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
override(narrow_state, "stream", () => undefined);
// Completed operator should return nothing
@ -951,7 +974,9 @@ test("operator_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("queries_with_spaces", () => {
test("queries_with_spaces", ({mock_template}) => {
mock_template("search_description.hbs", true, (_data, html) => html);
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});