mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 06:53:25 +00:00
For spectators, the chunk of page_params that originates from do_events_register isn’t assigned until ui_init.js. That means the TypeScript type of page_params is mostly a lie during module load time: reading a parameter too early silently results in undefined rather than the declared type, with unpredictable results later on. We want to make such an early read into an immediate runtime error, for both users and spectators consistently, and pave the way for runtime validation of the page_params type. As a second step, split out the subset of fields that pertain to the entire realm. Signed-off-by: Anders Kaseorg <anders@zulip.com>
1923 lines
69 KiB
JavaScript
1923 lines
69 KiB
JavaScript
"use strict";
|
|
|
|
const {strict: assert} = require("assert");
|
|
|
|
const {parseOneAddress} = require("email-addresses");
|
|
|
|
const {mock_esm, with_overrides, zrequire} = require("./lib/namespace");
|
|
const {run_test} = require("./lib/test");
|
|
const blueslip = require("./lib/zblueslip");
|
|
const $ = require("./lib/zjquery");
|
|
const {page_params, realm} = require("./lib/zpage_params");
|
|
|
|
const message_store = mock_esm("../src/message_store");
|
|
|
|
const resolved_topic = zrequire("../shared/src/resolved_topic");
|
|
const stream_data = zrequire("stream_data");
|
|
const people = zrequire("people");
|
|
const {Filter} = zrequire("../src/filter");
|
|
|
|
const me = {
|
|
email: "me@example.com",
|
|
user_id: 30,
|
|
full_name: "Me Myself",
|
|
};
|
|
|
|
const joe = {
|
|
email: "joe@example.com",
|
|
user_id: 31,
|
|
full_name: "joe",
|
|
};
|
|
|
|
const steve = {
|
|
email: "STEVE@foo.com",
|
|
user_id: 32,
|
|
full_name: "steve",
|
|
};
|
|
|
|
const alice = {
|
|
email: "alice@example.com",
|
|
user_id: 33,
|
|
full_name: "alice",
|
|
is_guest: true,
|
|
};
|
|
|
|
people.add_active_user(me);
|
|
people.add_active_user(joe);
|
|
people.add_active_user(steve);
|
|
people.add_active_user(alice);
|
|
people.initialize_current_user(me.user_id);
|
|
|
|
function assert_same_terms(result, terms) {
|
|
// If negated flag is undefined, we explicitly
|
|
// set it to false.
|
|
terms = terms.map(({negated = false, operator, operand}) => ({negated, operator, operand}));
|
|
assert.deepEqual(result, terms);
|
|
}
|
|
|
|
function get_predicate(raw_terms) {
|
|
const terms = raw_terms.map((op) => ({
|
|
operator: op[0],
|
|
operand: op[1],
|
|
}));
|
|
return new Filter(terms).predicate();
|
|
}
|
|
|
|
function make_sub(name, stream_id) {
|
|
const sub = {
|
|
name,
|
|
stream_id,
|
|
};
|
|
stream_data.add_sub(sub);
|
|
}
|
|
|
|
function test(label, f) {
|
|
run_test(label, (helpers) => {
|
|
stream_data.clear_subscriptions();
|
|
f(helpers);
|
|
});
|
|
}
|
|
|
|
test("basics", () => {
|
|
let terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "stream", operand: "exclude_stream", negated: true},
|
|
{operator: "topic", operand: "bar"},
|
|
];
|
|
let filter = new Filter(terms);
|
|
|
|
assert_same_terms(filter.terms(), terms);
|
|
assert.deepEqual(filter.operands("stream"), ["foo"]);
|
|
|
|
assert.ok(filter.has_operator("stream"));
|
|
assert.ok(!filter.has_operator("search"));
|
|
|
|
assert.ok(filter.has_operand("stream", "foo"));
|
|
assert.ok(!filter.has_operand("stream", "exclude_stream"));
|
|
assert.ok(!filter.has_operand("stream", "nada"));
|
|
|
|
assert.ok(!filter.is_keyword_search());
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.allow_use_first_unread_when_narrowing());
|
|
assert.ok(filter.includes_full_stream_history());
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
{operator: "search", operand: "pizza"},
|
|
];
|
|
filter = new Filter(terms);
|
|
|
|
assert.ok(filter.is_keyword_search());
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(!filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.allow_use_first_unread_when_narrowing());
|
|
assert.ok(!filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(filter.can_bucket_by("stream"));
|
|
assert.ok(filter.can_bucket_by("stream", "topic"));
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
{operator: "near", operand: 17},
|
|
];
|
|
filter = new Filter(terms);
|
|
|
|
assert.ok(!filter.is_keyword_search());
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.allow_use_first_unread_when_narrowing());
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(filter.can_bucket_by("stream"));
|
|
assert.ok(filter.can_bucket_by("stream", "topic"));
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
// If our only stream operator is negated, then for all intents and purposes,
|
|
// we don't consider ourselves to have a stream operator, because we don't
|
|
// want to have the stream in the tab bar or unsubscribe messaging, etc.
|
|
terms = [{operator: "stream", operand: "exclude", negated: true}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.has_operator("stream"));
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
// Negated searches are just like positive searches for our purposes, since
|
|
// the search logic happens on the backend and we need to have can_apply_locally()
|
|
// be false, and we want "Search results" in the tab bar.
|
|
terms = [{operator: "search", operand: "stop_word", negated: true}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(filter.has_operator("search"));
|
|
assert.ok(!filter.can_apply_locally());
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(!filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
// Similar logic applies to negated "has" searches.
|
|
terms = [{operator: "has", operand: "images", negated: true}];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.has_operator("has"));
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.can_apply_locally(true));
|
|
assert.ok(!filter.includes_full_stream_history());
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(!filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [{operator: "streams", operand: "public", negated: true}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.has_operator("streams"));
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(filter.has_negated_operand("streams", "public"));
|
|
assert.ok(!filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [{operator: "streams", operand: "public"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(filter.has_operator("streams"));
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.has_negated_operand("streams", "public"));
|
|
assert.ok(!filter.can_apply_locally());
|
|
assert.ok(filter.includes_full_stream_history());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [{operator: "is", operand: "dm"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.contains_only_private_messages());
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.has_operator("search"));
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
// "is:private" was renamed to "is:dm"
|
|
terms = [{operator: "is", operand: "private"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.has_operand("is", "dm"));
|
|
assert.ok(!filter.has_operand("is", "private"));
|
|
|
|
terms = [{operator: "is", operand: "mentioned"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(!filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.has_operator("search"));
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [{operator: "is", operand: "starred"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(!filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.has_operator("search"));
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [{operator: "dm", operand: "joe@example.com"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.is_non_huddle_pm());
|
|
assert.ok(filter.contains_only_private_messages());
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.has_operator("search"));
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(filter.is_conversation_view());
|
|
|
|
terms = [{operator: "dm", operand: "joe@example.com,jack@example.com"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.is_non_huddle_pm());
|
|
assert.ok(filter.contains_only_private_messages());
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(filter.is_conversation_view());
|
|
|
|
// "pm-with" was renamed to "dm"
|
|
terms = [{operator: "pm-with", operand: "joe@example.com"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.has_operator("dm"));
|
|
assert.ok(!filter.has_operator(" pm-with"));
|
|
|
|
terms = [{operator: "dm-including", operand: "joe@example.com"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.is_non_huddle_pm());
|
|
assert.ok(filter.contains_only_private_messages());
|
|
assert.ok(!filter.has_operator("search"));
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
// "group-pm-with" was replaced with "dm-including"
|
|
terms = [{operator: "group-pm-with", operand: "joe@example.com"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.has_operator("dm-including"));
|
|
assert.ok(!filter.has_operator("group-pm-with"));
|
|
|
|
terms = [{operator: "is", operand: "resolved"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.has_operator("search"));
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
// Highly complex query to exercise
|
|
// filter.supports_collapsing_recipients loop.
|
|
terms = [
|
|
{operator: "is", operand: "resolved", negated: true},
|
|
{operator: "is", operand: "dm", negated: true},
|
|
{operator: "stream", operand: "stream_name", negated: true},
|
|
{operator: "streams", operand: "web-public", negated: true},
|
|
{operator: "streams", operand: "public"},
|
|
{operator: "topic", operand: "patience", negated: true},
|
|
{operator: "in", operand: "all"},
|
|
];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(!filter.has_operator("search"));
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
// This next check verifies what is probably a bug; see the
|
|
// comment in the can_apply_locally implementation.
|
|
assert.ok(!filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(!filter.is_conversation_view());
|
|
|
|
terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
];
|
|
filter = new Filter(terms);
|
|
|
|
assert.ok(!filter.is_keyword_search());
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(filter.supports_collapsing_recipients());
|
|
assert.ok(!filter.contains_only_private_messages());
|
|
assert.ok(filter.allow_use_first_unread_when_narrowing());
|
|
assert.ok(filter.includes_full_stream_history());
|
|
assert.ok(filter.can_apply_locally());
|
|
assert.ok(!filter.is_personal_filter());
|
|
assert.ok(filter.is_conversation_view());
|
|
});
|
|
|
|
function assert_not_mark_read_with_has_operands(additional_terms_to_test) {
|
|
additional_terms_to_test = additional_terms_to_test || [];
|
|
let has_link_term = [{operator: "has", operand: "link"}];
|
|
let filter = new Filter([...additional_terms_to_test, ...has_link_term]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
has_link_term = [{operator: "has", operand: "link", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...has_link_term]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
has_link_term = [{operator: "has", operand: "image"}];
|
|
filter = new Filter([...additional_terms_to_test, ...has_link_term]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
has_link_term = [{operator: "has", operand: "image", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...has_link_term]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
has_link_term = [{operator: "has", operand: "attachment", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...has_link_term]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
has_link_term = [{operator: "has", operand: "attachment"}];
|
|
filter = new Filter([...additional_terms_to_test, ...has_link_term]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
}
|
|
function assert_not_mark_read_with_is_operands(additional_terms_to_test) {
|
|
additional_terms_to_test = additional_terms_to_test || [];
|
|
let is_operator = [{operator: "is", operand: "starred"}];
|
|
let filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "starred", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "mentioned"}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "mentioned", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "alerted"}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "alerted", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "unread"}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "unread", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
is_operator = [{operator: "is", operand: "resolved"}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
if (additional_terms_to_test.length === 0) {
|
|
assert.ok(filter.can_mark_messages_read());
|
|
} else {
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
}
|
|
|
|
is_operator = [{operator: "is", operand: "resolved", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...is_operator]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
}
|
|
|
|
function assert_not_mark_read_when_searching(additional_terms_to_test) {
|
|
additional_terms_to_test = additional_terms_to_test || [];
|
|
let search_op = [{operator: "search", operand: "keyword"}];
|
|
let filter = new Filter([...additional_terms_to_test, ...search_op]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
search_op = [{operator: "search", operand: "keyword", negated: true}];
|
|
filter = new Filter([...additional_terms_to_test, ...search_op]);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
}
|
|
|
|
test("can_mark_messages_read", () => {
|
|
assert_not_mark_read_with_has_operands();
|
|
assert_not_mark_read_with_is_operands();
|
|
assert_not_mark_read_when_searching();
|
|
|
|
const stream_term = [{operator: "stream", operand: "foo"}];
|
|
let filter = new Filter(stream_term);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert_not_mark_read_with_has_operands(stream_term);
|
|
assert_not_mark_read_with_is_operands(stream_term);
|
|
assert_not_mark_read_when_searching(stream_term);
|
|
|
|
const stream_negated_operator = [{operator: "stream", operand: "foo", negated: true}];
|
|
filter = new Filter(stream_negated_operator);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
const stream_topic_terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
];
|
|
filter = new Filter(stream_topic_terms);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert_not_mark_read_with_has_operands(stream_topic_terms);
|
|
assert_not_mark_read_with_is_operands(stream_topic_terms);
|
|
assert_not_mark_read_when_searching(stream_topic_terms);
|
|
|
|
const stream_negated_topic_terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar", negated: true},
|
|
];
|
|
filter = new Filter(stream_negated_topic_terms);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
const dm = [{operator: "dm", operand: "joe@example.com,"}];
|
|
|
|
const dm_negated = [{operator: "dm", operand: "joe@example.com,", negated: true}];
|
|
|
|
const dm_group = [{operator: "dm", operand: "joe@example.com,STEVE@foo.com"}];
|
|
filter = new Filter(dm);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
filter = new Filter(dm_negated);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
filter = new Filter(dm_group);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert_not_mark_read_with_is_operands(dm_group);
|
|
assert_not_mark_read_with_is_operands(dm);
|
|
assert_not_mark_read_with_has_operands(dm_group);
|
|
assert_not_mark_read_with_has_operands(dm);
|
|
assert_not_mark_read_when_searching(dm_group);
|
|
assert_not_mark_read_when_searching(dm);
|
|
|
|
const is_dm = [{operator: "is", operand: "dm"}];
|
|
filter = new Filter(is_dm);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert_not_mark_read_with_is_operands(is_dm);
|
|
assert_not_mark_read_with_has_operands(is_dm);
|
|
assert_not_mark_read_when_searching(is_dm);
|
|
|
|
const in_all = [{operator: "in", operand: "all"}];
|
|
filter = new Filter(in_all);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert_not_mark_read_with_is_operands(in_all);
|
|
assert_not_mark_read_with_has_operands(in_all);
|
|
assert_not_mark_read_when_searching(in_all);
|
|
|
|
const in_home = [{operator: "in", operand: "home"}];
|
|
const in_home_negated = [{operator: "in", operand: "home", negated: true}];
|
|
filter = new Filter(in_home);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert_not_mark_read_with_is_operands(in_home);
|
|
assert_not_mark_read_with_has_operands(in_home);
|
|
assert_not_mark_read_when_searching(in_home);
|
|
filter = new Filter(in_home_negated);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
// Do not mark messages as read when in an unsupported 'in:*' filter.
|
|
const in_random = [{operator: "in", operand: "xxxxxxxxx"}];
|
|
const in_random_negated = [{operator: "in", operand: "xxxxxxxxx", negated: true}];
|
|
filter = new Filter(in_random);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
filter = new Filter(in_random_negated);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
|
|
// test caching of term types
|
|
// init and stub
|
|
filter = new Filter(dm);
|
|
filter.stub = filter.calc_can_mark_messages_read;
|
|
filter.calc_can_mark_messages_read = function () {
|
|
this.calc_can_mark_messages_read_called = true;
|
|
return this.stub();
|
|
};
|
|
|
|
// uncached trial
|
|
filter.calc_can_mark_messages_read_called = false;
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(filter.calc_can_mark_messages_read_called);
|
|
|
|
// cached trial
|
|
filter.calc_can_mark_messages_read_called = false;
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(!filter.calc_can_mark_messages_read_called);
|
|
});
|
|
|
|
test("show_first_unread", () => {
|
|
let terms = [{operator: "is", operand: "any"}];
|
|
let filter = new Filter(terms);
|
|
assert.ok(filter.allow_use_first_unread_when_narrowing());
|
|
|
|
terms = [{operator: "search", operand: "query to search"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.allow_use_first_unread_when_narrowing());
|
|
|
|
filter = new Filter([]);
|
|
assert.ok(filter.can_mark_messages_read());
|
|
assert.ok(filter.allow_use_first_unread_when_narrowing());
|
|
|
|
// Side case
|
|
terms = [{operator: "is", operand: "any"}];
|
|
filter = new Filter(terms);
|
|
assert.ok(!filter.can_mark_messages_read());
|
|
assert.ok(filter.allow_use_first_unread_when_narrowing());
|
|
});
|
|
|
|
test("filter_with_new_params_topic", () => {
|
|
const terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "old topic"},
|
|
];
|
|
const filter = new Filter(terms);
|
|
|
|
assert.ok(filter.has_topic("foo", "old topic"));
|
|
assert.ok(!filter.has_topic("wrong", "old topic"));
|
|
assert.ok(!filter.has_topic("foo", "wrong"));
|
|
|
|
const new_filter = filter.filter_with_new_params({
|
|
operator: "topic",
|
|
operand: "new topic",
|
|
});
|
|
|
|
assert.deepEqual(new_filter.operands("stream"), ["foo"]);
|
|
assert.deepEqual(new_filter.operands("topic"), ["new topic"]);
|
|
});
|
|
|
|
test("filter_with_new_params_stream", () => {
|
|
const terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "old topic"},
|
|
];
|
|
const filter = new Filter(terms);
|
|
|
|
assert.ok(filter.has_topic("foo", "old topic"));
|
|
assert.ok(!filter.has_topic("wrong", "old topic"));
|
|
assert.ok(!filter.has_topic("foo", "wrong"));
|
|
|
|
const new_filter = filter.filter_with_new_params({
|
|
operator: "stream",
|
|
operand: "new stream",
|
|
});
|
|
|
|
assert.deepEqual(new_filter.operands("stream"), ["new stream"]);
|
|
assert.deepEqual(new_filter.operands("topic"), ["old topic"]);
|
|
});
|
|
|
|
test("new_style_terms", () => {
|
|
const term = {
|
|
operator: "stream",
|
|
operand: "foo",
|
|
};
|
|
const terms = [term];
|
|
const filter = new Filter(terms);
|
|
|
|
assert.deepEqual(filter.operands("stream"), ["foo"]);
|
|
assert.ok(filter.can_bucket_by("stream"));
|
|
});
|
|
|
|
test("public_terms", ({override}) => {
|
|
stream_data.clear_subscriptions();
|
|
let terms = [
|
|
{operator: "stream", operand: "some_stream"},
|
|
{operator: "in", operand: "all"},
|
|
{operator: "topic", operand: "bar"},
|
|
];
|
|
|
|
let filter = new Filter(terms);
|
|
override(page_params, "narrow_stream", undefined);
|
|
assert_same_terms(filter.public_terms(), terms);
|
|
assert.ok(filter.can_bucket_by("stream"));
|
|
|
|
terms = [{operator: "stream", operand: "default"}];
|
|
filter = new Filter(terms);
|
|
override(page_params, "narrow_stream", "default");
|
|
assert_same_terms(filter.public_terms(), []);
|
|
});
|
|
|
|
test("redundancies", () => {
|
|
let terms;
|
|
let filter;
|
|
|
|
terms = [
|
|
{operator: "dm", operand: "joe@example.com,"},
|
|
{operator: "is", operand: "dm"},
|
|
];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.can_bucket_by("dm"));
|
|
|
|
terms = [
|
|
{operator: "dm", operand: "joe@example.com,", negated: true},
|
|
{operator: "is", operand: "dm"},
|
|
];
|
|
filter = new Filter(terms);
|
|
assert.ok(filter.can_bucket_by("is-dm", "not-dm"));
|
|
});
|
|
|
|
test("canonicalization", () => {
|
|
assert.equal(Filter.canonicalize_operator("Is"), "is");
|
|
assert.equal(Filter.canonicalize_operator("Stream"), "stream");
|
|
assert.equal(Filter.canonicalize_operator("Subject"), "topic");
|
|
assert.equal(Filter.canonicalize_operator("FROM"), "sender");
|
|
|
|
let term;
|
|
term = Filter.canonicalize_term({operator: "Stream", operand: "Denmark"});
|
|
assert.equal(term.operator, "stream");
|
|
assert.equal(term.operand, "Denmark");
|
|
|
|
term = Filter.canonicalize_term({operator: "sender", operand: "me"});
|
|
assert.equal(term.operator, "sender");
|
|
assert.equal(term.operand, "me@example.com");
|
|
|
|
// "pm-with" was renamed to "dm"
|
|
term = Filter.canonicalize_term({operator: "pm-with", operand: "me"});
|
|
assert.equal(term.operator, "dm");
|
|
assert.equal(term.operand, "me@example.com");
|
|
|
|
// "group-pm-with" was replaced with "dm-including"
|
|
term = Filter.canonicalize_term({operator: "group-pm-with", operand: "joe@example.com"});
|
|
assert.equal(term.operator, "dm-including");
|
|
assert.equal(term.operand, "joe@example.com");
|
|
|
|
term = Filter.canonicalize_term({operator: "search", operand: "foo"});
|
|
assert.equal(term.operator, "search");
|
|
assert.equal(term.operand, "foo");
|
|
|
|
term = Filter.canonicalize_term({operator: "search", operand: "fOO"});
|
|
assert.equal(term.operator, "search");
|
|
assert.equal(term.operand, "foo");
|
|
|
|
term = Filter.canonicalize_term({operator: "search", operand: 123});
|
|
assert.equal(term.operator, "search");
|
|
assert.equal(term.operand, "123");
|
|
|
|
term = Filter.canonicalize_term({operator: "search", operand: "abc “xyz”"});
|
|
assert.equal(term.operator, "search");
|
|
assert.equal(term.operand, 'abc "xyz"');
|
|
|
|
term = Filter.canonicalize_term({operator: "has", operand: "attachments"});
|
|
assert.equal(term.operator, "has");
|
|
assert.equal(term.operand, "attachment");
|
|
|
|
term = Filter.canonicalize_term({operator: "has", operand: "images"});
|
|
assert.equal(term.operator, "has");
|
|
assert.equal(term.operand, "image");
|
|
|
|
term = Filter.canonicalize_term({operator: "has", operand: "links"});
|
|
assert.equal(term.operator, "has");
|
|
assert.equal(term.operand, "link");
|
|
});
|
|
|
|
test("predicate_basics", () => {
|
|
// Predicates are functions that accept a message object with the message
|
|
// attributes (not content), and return true if the message belongs in a
|
|
// given narrow. If the narrow parameters include a search, the predicate
|
|
// passes through all messages.
|
|
//
|
|
// To keep these tests simple, we only pass objects with a few relevant attributes
|
|
// rather than full-fledged message objects.
|
|
|
|
const stream_id = 42;
|
|
make_sub("Foo", stream_id);
|
|
let predicate = get_predicate([
|
|
["stream", "Foo"],
|
|
["topic", "Bar"],
|
|
]);
|
|
|
|
assert.ok(predicate({type: "stream", stream_id, topic: "bar"}));
|
|
assert.ok(!predicate({type: "stream", stream_id, topic: "whatever"}));
|
|
// 9999999 doesn't exist, testing no match
|
|
assert.ok(!predicate({type: "stream", stream_id: 9999999}));
|
|
assert.ok(!predicate({type: "private"}));
|
|
|
|
// For old streams that we are no longer subscribed to, we may not have
|
|
// a sub, but these should still match by stream name.
|
|
const old_sub = {
|
|
name: "old-Stream",
|
|
stream_id: 5,
|
|
subscribed: false,
|
|
};
|
|
stream_data.add_sub(old_sub);
|
|
predicate = get_predicate([
|
|
["stream", "old-Stream"],
|
|
["topic", "Bar"],
|
|
]);
|
|
assert.ok(predicate({type: "stream", stream_id: 5, topic: "bar"}));
|
|
// 99999 doesn't exist, testing no match
|
|
assert.ok(!predicate({type: "stream", stream_id: 99999, topic: "whatever"}));
|
|
|
|
predicate = get_predicate([["search", "emoji"]]);
|
|
assert.ok(predicate({}));
|
|
|
|
predicate = get_predicate([["topic", "Bar"]]);
|
|
assert.ok(!predicate({type: "private"}));
|
|
|
|
predicate = get_predicate([["is", "dm"]]);
|
|
assert.ok(predicate({type: "private"}));
|
|
assert.ok(!predicate({type: "stream"}));
|
|
|
|
predicate = get_predicate([["streams", "public"]]);
|
|
assert.ok(predicate({}));
|
|
|
|
predicate = get_predicate([["is", "starred"]]);
|
|
assert.ok(predicate({starred: true}));
|
|
assert.ok(!predicate({starred: false}));
|
|
|
|
predicate = get_predicate([["is", "unread"]]);
|
|
assert.ok(predicate({unread: true}));
|
|
assert.ok(!predicate({unread: false}));
|
|
|
|
predicate = get_predicate([["is", "alerted"]]);
|
|
assert.ok(predicate({alerted: true}));
|
|
assert.ok(!predicate({alerted: false}));
|
|
assert.ok(!predicate({}));
|
|
|
|
predicate = get_predicate([["is", "mentioned"]]);
|
|
assert.ok(predicate({mentioned: true}));
|
|
assert.ok(!predicate({mentioned: false}));
|
|
|
|
predicate = get_predicate([["in", "all"]]);
|
|
assert.ok(predicate({}));
|
|
|
|
predicate = get_predicate([["is", "resolved"]]);
|
|
const resolved_topic_name = resolved_topic.resolve_name("foo");
|
|
assert.ok(predicate({type: "stream", topic: resolved_topic_name}));
|
|
assert.ok(!predicate({topic: resolved_topic_name}));
|
|
assert.ok(!predicate({type: "stream", topic: "foo"}));
|
|
|
|
const unknown_stream_id = 999;
|
|
predicate = get_predicate([["in", "home"]]);
|
|
assert.ok(!predicate({stream_id: unknown_stream_id, stream: "unknown"}));
|
|
assert.ok(predicate({type: "private"}));
|
|
|
|
make_sub("kiosk", 1234);
|
|
with_overrides(({override}) => {
|
|
override(page_params, "narrow_stream", "kiosk");
|
|
assert.ok(predicate({stream_id: 1234}));
|
|
});
|
|
|
|
predicate = get_predicate([["near", 5]]);
|
|
assert.ok(predicate({}));
|
|
|
|
predicate = get_predicate([["id", 5]]);
|
|
assert.ok(predicate({id: 5}));
|
|
assert.ok(!predicate({id: 6}));
|
|
|
|
predicate = get_predicate([
|
|
["id", 5],
|
|
["topic", "lunch"],
|
|
]);
|
|
assert.ok(predicate({type: "stream", id: 5, topic: "lunch"}));
|
|
assert.ok(!predicate({type: "stream", id: 5, topic: "dinner"}));
|
|
|
|
predicate = get_predicate([["sender", "Joe@example.com"]]);
|
|
assert.ok(predicate({sender_id: joe.user_id}));
|
|
assert.ok(!predicate({sender_email: steve.user_id}));
|
|
|
|
predicate = get_predicate([["dm", "Joe@example.com"]]);
|
|
assert.ok(
|
|
predicate({
|
|
type: "private",
|
|
display_recipient: [{id: joe.user_id}],
|
|
}),
|
|
);
|
|
assert.ok(
|
|
!predicate({
|
|
type: "private",
|
|
display_recipient: [{id: steve.user_id}],
|
|
}),
|
|
);
|
|
assert.ok(
|
|
!predicate({
|
|
type: "private",
|
|
display_recipient: [{id: 999999}],
|
|
}),
|
|
);
|
|
assert.ok(!predicate({type: "stream"}));
|
|
|
|
predicate = get_predicate([["dm", "Joe@example.com,steve@foo.com"]]);
|
|
assert.ok(
|
|
predicate({
|
|
type: "private",
|
|
display_recipient: [{id: joe.user_id}, {id: steve.user_id}],
|
|
}),
|
|
);
|
|
|
|
// Make sure your own email is ignored
|
|
predicate = get_predicate([["dm", "Joe@example.com,steve@foo.com,me@example.com"]]);
|
|
assert.ok(
|
|
predicate({
|
|
type: "private",
|
|
display_recipient: [{id: joe.user_id}, {id: steve.user_id}],
|
|
}),
|
|
);
|
|
|
|
predicate = get_predicate([["dm", "nobody@example.com"]]);
|
|
assert.ok(
|
|
!predicate({
|
|
type: "private",
|
|
display_recipient: [{id: joe.user_id}],
|
|
}),
|
|
);
|
|
|
|
predicate = get_predicate([["dm-including", "nobody@example.com"]]);
|
|
assert.ok(
|
|
!predicate({
|
|
type: "private",
|
|
display_recipient: [{id: joe.user_id}, {id: me.user_id}],
|
|
}),
|
|
);
|
|
|
|
predicate = get_predicate([["dm-including", "Joe@example.com"]]);
|
|
assert.ok(
|
|
predicate({
|
|
type: "private",
|
|
display_recipient: [{id: joe.user_id}, {id: steve.user_id}, {id: me.user_id}],
|
|
}),
|
|
);
|
|
assert.ok(
|
|
predicate({
|
|
type: "private",
|
|
display_recipient: [{id: joe.user_id}, {id: me.user_id}],
|
|
}),
|
|
);
|
|
assert.ok(
|
|
!predicate({
|
|
type: "private",
|
|
display_recipient: [{id: steve.user_id}, {id: me.user_id}],
|
|
}),
|
|
);
|
|
assert.ok(!predicate({type: "stream"}));
|
|
|
|
const img_msg = {
|
|
content:
|
|
'<p><a href="/user_uploads/randompath/test.jpeg">test.jpeg</a></p><div class="message_inline_image"><a href="/user_uploads/randompath/test.jpeg" title="test.jpeg"><img src="/user_uploads/randompath/test.jpeg"></a></div>',
|
|
};
|
|
|
|
const link_msg = {
|
|
content: '<p><a href="http://chat.zulip.org">chat.zulip.org</a></p>',
|
|
};
|
|
|
|
const non_img_attachment_msg = {
|
|
content: '<p><a href="/user_uploads/randompath/attachment.ext">attachment.ext</a></p>',
|
|
};
|
|
|
|
const no_has_filter_matching_msg = {
|
|
content: "<p>Testing</p>",
|
|
};
|
|
|
|
predicate = get_predicate([["has", "non_valid_operand"]]);
|
|
assert.ok(!predicate(img_msg));
|
|
assert.ok(!predicate(non_img_attachment_msg));
|
|
assert.ok(!predicate(link_msg));
|
|
assert.ok(!predicate(no_has_filter_matching_msg));
|
|
|
|
// HTML content of message is used to determine if image have link, image or attachment.
|
|
// We are using jquery to parse the html and find existence of relevant tags/elements.
|
|
// In tests we need to stub the calls to jquery so using zjquery's .set_find_results method.
|
|
function set_find_results_for_msg_content(msg, jquery_selector, results) {
|
|
$(`<div>${msg.content}</div>`).set_find_results(jquery_selector, results);
|
|
}
|
|
|
|
const has_link = get_predicate([["has", "link"]]);
|
|
set_find_results_for_msg_content(img_msg, "a", ["stub"]);
|
|
assert.ok(has_link(img_msg));
|
|
set_find_results_for_msg_content(non_img_attachment_msg, "a", ["stub"]);
|
|
assert.ok(has_link(non_img_attachment_msg));
|
|
set_find_results_for_msg_content(link_msg, "a", ["stub"]);
|
|
assert.ok(has_link(link_msg));
|
|
set_find_results_for_msg_content(no_has_filter_matching_msg, "a", false);
|
|
assert.ok(!has_link(no_has_filter_matching_msg));
|
|
|
|
const has_attachment = get_predicate([["has", "attachment"]]);
|
|
set_find_results_for_msg_content(img_msg, "a[href^='/user_uploads']", ["stub"]);
|
|
assert.ok(has_attachment(img_msg));
|
|
set_find_results_for_msg_content(non_img_attachment_msg, "a[href^='/user_uploads']", ["stub"]);
|
|
assert.ok(has_attachment(non_img_attachment_msg));
|
|
set_find_results_for_msg_content(link_msg, "a[href^='/user_uploads']", false);
|
|
assert.ok(!has_attachment(link_msg));
|
|
set_find_results_for_msg_content(no_has_filter_matching_msg, "a[href^='/user_uploads']", false);
|
|
assert.ok(!has_attachment(no_has_filter_matching_msg));
|
|
|
|
const has_image = get_predicate([["has", "image"]]);
|
|
set_find_results_for_msg_content(img_msg, ".message_inline_image", ["stub"]);
|
|
assert.ok(has_image(img_msg));
|
|
set_find_results_for_msg_content(non_img_attachment_msg, ".message_inline_image", false);
|
|
assert.ok(!has_image(non_img_attachment_msg));
|
|
set_find_results_for_msg_content(link_msg, ".message_inline_image", false);
|
|
assert.ok(!has_image(link_msg));
|
|
set_find_results_for_msg_content(no_has_filter_matching_msg, ".message_inline_image", false);
|
|
assert.ok(!has_image(no_has_filter_matching_msg));
|
|
});
|
|
|
|
test("negated_predicates", () => {
|
|
let predicate;
|
|
let narrow;
|
|
|
|
const social_stream_id = 555;
|
|
make_sub("social", social_stream_id);
|
|
|
|
narrow = [{operator: "stream", operand: "social", negated: true}];
|
|
predicate = new Filter(narrow).predicate();
|
|
assert.ok(predicate({type: "stream", stream_id: 999999}));
|
|
assert.ok(!predicate({type: "stream", stream_id: social_stream_id}));
|
|
|
|
narrow = [{operator: "streams", operand: "public", negated: true}];
|
|
predicate = new Filter(narrow).predicate();
|
|
assert.ok(predicate({}));
|
|
});
|
|
|
|
function test_mit_exceptions() {
|
|
const foo_stream_id = 555;
|
|
make_sub("Foo", foo_stream_id);
|
|
let predicate = get_predicate([
|
|
["stream", "Foo"],
|
|
["topic", "personal"],
|
|
]);
|
|
assert.ok(predicate({type: "stream", stream_id: foo_stream_id, topic: "personal"}));
|
|
assert.ok(predicate({type: "stream", stream_id: foo_stream_id, topic: ""}));
|
|
// 9999 doesn't correspond to any stream
|
|
assert.ok(!predicate({type: "stream", stream_id: 9999}));
|
|
assert.ok(!predicate({type: "stream", stream_id: foo_stream_id, topic: "whatever"}));
|
|
assert.ok(!predicate({type: "private"}));
|
|
|
|
predicate = get_predicate([
|
|
["stream", "Foo"],
|
|
["topic", "bar"],
|
|
]);
|
|
assert.ok(predicate({type: "stream", stream_id: foo_stream_id, topic: "bar.d"}));
|
|
|
|
// Try to get the MIT regex to explode for an empty stream.
|
|
let terms = [
|
|
{operator: "stream", operand: ""},
|
|
{operator: "topic", operand: "bar"},
|
|
];
|
|
predicate = new Filter(terms).predicate();
|
|
assert.ok(!predicate({type: "stream", stream_id: foo_stream_id, topic: "bar"}));
|
|
|
|
// Try to get the MIT regex to explode for an empty topic.
|
|
terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: ""},
|
|
];
|
|
predicate = new Filter(terms).predicate();
|
|
assert.ok(!predicate({type: "stream", stream_id: foo_stream_id, topic: "bar"}));
|
|
}
|
|
|
|
test("mit_exceptions", ({override}) => {
|
|
override(realm, "realm_is_zephyr_mirror_realm", true);
|
|
test_mit_exceptions();
|
|
});
|
|
|
|
test("predicate_edge_cases", () => {
|
|
let predicate;
|
|
// The code supports undefined as an operator to Filter, which results
|
|
// in a predicate that accepts any message.
|
|
predicate = new Filter([]).predicate();
|
|
assert.ok(predicate({}));
|
|
|
|
// Upstream code should prevent Filter.predicate from being called with
|
|
// invalid operator/operand combinations, but right now we just silently
|
|
// return a function that accepts all messages.
|
|
predicate = get_predicate([["in", "bogus"]]);
|
|
assert.ok(!predicate({}));
|
|
|
|
predicate = get_predicate([["bogus", 33]]);
|
|
assert.ok(predicate({}));
|
|
|
|
predicate = get_predicate([["is", "bogus"]]);
|
|
assert.ok(!predicate({}));
|
|
|
|
// Exercise caching feature.
|
|
const stream_id = 101;
|
|
make_sub("Off topic", stream_id);
|
|
const terms = [
|
|
{operator: "stream", operand: "Off topic"},
|
|
{operator: "topic", operand: "Mars"},
|
|
];
|
|
const filter = new Filter(terms);
|
|
filter.predicate();
|
|
predicate = filter.predicate(); // get cached version
|
|
assert.ok(predicate({type: "stream", stream_id, topic: "Mars"}));
|
|
});
|
|
|
|
test("parse", () => {
|
|
let string;
|
|
let terms;
|
|
|
|
function _test() {
|
|
const result = Filter.parse(string);
|
|
assert_same_terms(result, terms);
|
|
}
|
|
|
|
string = "stream:Foo topic:Bar yo";
|
|
terms = [
|
|
{operator: "stream", operand: "Foo"},
|
|
{operator: "topic", operand: "Bar"},
|
|
{operator: "search", operand: "yo"},
|
|
];
|
|
_test();
|
|
|
|
string = "dm:leo+test@zulip.com";
|
|
terms = [{operator: "dm", operand: "leo+test@zulip.com"}];
|
|
_test();
|
|
|
|
string = "sender:leo+test@zulip.com";
|
|
terms = [{operator: "sender", operand: "leo+test@zulip.com"}];
|
|
_test();
|
|
|
|
string = "stream:With+Space";
|
|
terms = [{operator: "stream", operand: "With Space"}];
|
|
_test();
|
|
|
|
string = 'stream:"with quoted space" topic:and separate';
|
|
terms = [
|
|
{operator: "stream", operand: "with quoted space"},
|
|
{operator: "topic", operand: "and"},
|
|
{operator: "search", operand: "separate"},
|
|
];
|
|
_test();
|
|
|
|
string = 'stream:"unclosed quote';
|
|
terms = [{operator: "stream", operand: "unclosed quote"}];
|
|
_test();
|
|
|
|
string = 'stream:""';
|
|
terms = [{operator: "stream", operand: ""}];
|
|
_test();
|
|
|
|
string = "https://www.google.com";
|
|
terms = [{operator: "search", operand: "https://www.google.com"}];
|
|
_test();
|
|
|
|
string = "stream:foo -stream:exclude";
|
|
terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "stream", operand: "exclude", negated: true},
|
|
];
|
|
_test();
|
|
|
|
string = "text stream:foo more text";
|
|
terms = [
|
|
{operator: "search", operand: "text"},
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "search", operand: "more text"},
|
|
];
|
|
_test();
|
|
|
|
string = "text streams:public more text";
|
|
terms = [
|
|
{operator: "search", operand: "text"},
|
|
{operator: "streams", operand: "public"},
|
|
{operator: "search", operand: "more text"},
|
|
];
|
|
_test();
|
|
|
|
string = "streams:public";
|
|
terms = [{operator: "streams", operand: "public"}];
|
|
_test();
|
|
|
|
string = "-streams:public";
|
|
terms = [{operator: "streams", operand: "public", negated: true}];
|
|
_test();
|
|
|
|
string = "stream:foo :emoji: are cool";
|
|
terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "search", operand: ":emoji: are cool"},
|
|
];
|
|
_test();
|
|
|
|
string = ":stream: stream:foo :emoji: are cool";
|
|
terms = [
|
|
{operator: "search", operand: ":stream:"},
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "search", operand: ":emoji: are cool"},
|
|
];
|
|
_test();
|
|
|
|
string = ":stream: stream:foo -:emoji: are cool";
|
|
terms = [
|
|
{operator: "search", operand: ":stream:"},
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "search", operand: "-:emoji: are cool"},
|
|
];
|
|
_test();
|
|
|
|
string = "";
|
|
terms = [];
|
|
_test();
|
|
|
|
string = 'stream: separated topic: "with space"';
|
|
terms = [
|
|
{operator: "stream", operand: "separated"},
|
|
{operator: "topic", operand: "with space"},
|
|
];
|
|
_test();
|
|
});
|
|
|
|
test("unparse", () => {
|
|
let string;
|
|
let terms;
|
|
|
|
terms = [
|
|
{operator: "stream", operand: "Foo"},
|
|
{operator: "topic", operand: "Bar", negated: true},
|
|
{operator: "search", operand: "yo"},
|
|
];
|
|
string = "stream:Foo -topic:Bar yo";
|
|
assert.deepEqual(Filter.unparse(terms), string);
|
|
|
|
terms = [
|
|
{operator: "streams", operand: "public"},
|
|
{operator: "search", operand: "text"},
|
|
];
|
|
|
|
string = "streams:public text";
|
|
assert.deepEqual(Filter.unparse(terms), string);
|
|
|
|
terms = [{operator: "streams", operand: "public"}];
|
|
string = "streams:public";
|
|
assert.deepEqual(Filter.unparse(terms), string);
|
|
|
|
terms = [{operator: "streams", operand: "public", negated: true}];
|
|
string = "-streams:public";
|
|
assert.deepEqual(Filter.unparse(terms), string);
|
|
|
|
terms = [{operator: "id", operand: 50}];
|
|
string = "id:50";
|
|
assert.deepEqual(Filter.unparse(terms), string);
|
|
|
|
terms = [{operator: "near", operand: 150}];
|
|
string = "near:150";
|
|
assert.deepEqual(Filter.unparse(terms), string);
|
|
|
|
terms = [{operator: "", operand: ""}];
|
|
string = "";
|
|
assert.deepEqual(Filter.unparse(terms), string);
|
|
});
|
|
|
|
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";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [{operator: "streams", operand: "public", negated: true}];
|
|
string = "exclude streams public";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "stream", operand: "devel"},
|
|
{operator: "is", operand: "starred"},
|
|
];
|
|
string = "stream devel, starred messages";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "stream", operand: "river"},
|
|
{operator: "is", operand: "unread"},
|
|
];
|
|
string = "stream river, unread messages";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "stream", operand: "devel"},
|
|
{operator: "topic", operand: "JS"},
|
|
];
|
|
string = "stream devel > JS";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "is", operand: "dm"},
|
|
{operator: "search", operand: "lunch"},
|
|
];
|
|
string = "direct messages, search for lunch";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [{operator: "id", operand: 99}];
|
|
string = "message ID 99";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [{operator: "in", operand: "home"}];
|
|
string = "messages in home";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [{operator: "is", operand: "mentioned"}];
|
|
string = "@-mentions";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [{operator: "is", operand: "alerted"}];
|
|
string = "alerted messages";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [{operator: "is", operand: "resolved"}];
|
|
string = "topics marked as resolved";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [{operator: "is", operand: "something_we_do_not_support"}];
|
|
string = "invalid something_we_do_not_support operand for is operator";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
// this should be unreachable, but just in case
|
|
narrow = [{operator: "bogus", operand: "foo"}];
|
|
string = "unknown operator";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "stream", operand: "devel"},
|
|
{operator: "topic", operand: "JS", negated: true},
|
|
];
|
|
string = "stream devel, exclude topic JS";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "is", operand: "dm"},
|
|
{operator: "search", operand: "lunch", negated: true},
|
|
];
|
|
string = "direct messages, exclude lunch";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "stream", operand: "devel"},
|
|
{operator: "is", operand: "starred", negated: true},
|
|
];
|
|
string = "stream devel, exclude starred messages";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "stream", operand: "devel"},
|
|
{operator: "has", operand: "image", negated: true},
|
|
];
|
|
string = "stream devel, exclude messages with one or more image";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "has", operand: "abc", negated: true},
|
|
{operator: "stream", operand: "devel"},
|
|
];
|
|
string = "invalid abc operand for has operator, stream devel";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [
|
|
{operator: "has", operand: "image", negated: true},
|
|
{operator: "stream", operand: "devel"},
|
|
];
|
|
string = "exclude messages with one or more image, stream devel";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
|
|
narrow = [];
|
|
string = "all messages";
|
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
|
});
|
|
|
|
test("can_bucket_by", () => {
|
|
let terms = [{operator: "stream", operand: "My stream"}];
|
|
let filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("stream"), true);
|
|
assert.equal(filter.can_bucket_by("stream", "topic"), false);
|
|
assert.equal(filter.can_bucket_by("dm"), false);
|
|
|
|
terms = [
|
|
// try a non-orthodox ordering
|
|
{operator: "topic", operand: "My topic"},
|
|
{operator: "stream", operand: "My stream"},
|
|
];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("stream"), true);
|
|
assert.equal(filter.can_bucket_by("stream", "topic"), true);
|
|
assert.equal(filter.can_bucket_by("dm"), false);
|
|
|
|
terms = [
|
|
{operator: "stream", operand: "My stream", negated: true},
|
|
{operator: "topic", operand: "My topic"},
|
|
];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("stream"), false);
|
|
assert.equal(filter.can_bucket_by("stream", "topic"), false);
|
|
assert.equal(filter.can_bucket_by("dm"), false);
|
|
|
|
terms = [{operator: "dm", operand: "foo@example.com", negated: true}];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("stream"), false);
|
|
assert.equal(filter.can_bucket_by("stream", "topic"), false);
|
|
assert.equal(filter.can_bucket_by("dm"), false);
|
|
|
|
terms = [{operator: "dm", operand: "foo@example.com,bar@example.com"}];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("stream"), false);
|
|
assert.equal(filter.can_bucket_by("stream", "topic"), false);
|
|
assert.equal(filter.can_bucket_by("dm"), true);
|
|
assert.equal(filter.can_bucket_by("is-mentioned"), false);
|
|
assert.equal(filter.can_bucket_by("is-dm"), false);
|
|
|
|
terms = [{operator: "is", operand: "dm"}];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("is-mentioned"), false);
|
|
assert.equal(filter.can_bucket_by("is-dm"), true);
|
|
|
|
terms = [{operator: "is", operand: "mentioned"}];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("is-mentioned"), true);
|
|
assert.equal(filter.can_bucket_by("is-dm"), false);
|
|
|
|
terms = [
|
|
{operator: "is", operand: "mentioned"},
|
|
{operator: "is", operand: "starred"},
|
|
];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("is-mentioned"), true);
|
|
assert.equal(filter.can_bucket_by("is-dm"), false);
|
|
|
|
// The call below returns false for somewhat arbitrary
|
|
// reasons -- we say is-dm has precedence over
|
|
// is-starred.
|
|
assert.equal(filter.can_bucket_by("is-starred"), false);
|
|
|
|
terms = [{operator: "is", operand: "mentioned", negated: true}];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("is-mentioned"), false);
|
|
assert.equal(filter.can_bucket_by("is-dm"), false);
|
|
|
|
terms = [{operator: "is", operand: "resolved"}];
|
|
filter = new Filter(terms);
|
|
assert.equal(filter.can_bucket_by("stream", "topic"), false);
|
|
assert.equal(filter.can_bucket_by("stream"), false);
|
|
assert.equal(filter.can_bucket_by("dm"), false);
|
|
assert.equal(filter.can_bucket_by("is-mentioned"), false);
|
|
assert.equal(filter.can_bucket_by("is-dm"), false);
|
|
});
|
|
|
|
test("term_type", () => {
|
|
function assert_term_type(term, expected_term_type) {
|
|
assert.equal(Filter.term_type(term), expected_term_type);
|
|
}
|
|
|
|
function term(operator, operand, negated) {
|
|
return {
|
|
operator,
|
|
operand,
|
|
negated,
|
|
};
|
|
}
|
|
|
|
assert_term_type(term("streams", "public"), "streams-public");
|
|
assert_term_type(term("stream", "whatever"), "stream");
|
|
assert_term_type(term("dm", "whomever"), "dm");
|
|
assert_term_type(term("dm", "whomever", true), "not-dm");
|
|
assert_term_type(term("is", "dm"), "is-dm");
|
|
assert_term_type(term("is", "private"), "is-private");
|
|
assert_term_type(term("has", "link"), "has-link");
|
|
assert_term_type(term("has", "attachment", true), "not-has-attachment");
|
|
|
|
function assert_term_sort(in_terms, expected) {
|
|
const sorted_terms = Filter.sorted_term_types(in_terms);
|
|
assert.deepEqual(sorted_terms, expected);
|
|
}
|
|
|
|
assert_term_sort(["topic", "stream", "sender"], ["stream", "topic", "sender"]);
|
|
|
|
assert_term_sort(
|
|
["has-link", "near", "is-unread", "dm"],
|
|
["dm", "near", "is-unread", "has-link"],
|
|
);
|
|
|
|
assert_term_sort(["bogus", "stream", "topic"], ["stream", "topic", "bogus"]);
|
|
assert_term_sort(["stream", "topic", "stream"], ["stream", "stream", "topic"]);
|
|
|
|
assert_term_sort(["search", "streams-public"], ["streams-public", "search"]);
|
|
|
|
const terms = [
|
|
{operator: "topic", operand: "lunch"},
|
|
{operator: "sender", operand: "steve@foo.com"},
|
|
{operator: "stream", operand: "Verona"},
|
|
];
|
|
let filter = new Filter(terms);
|
|
const term_types = filter.sorted_term_types();
|
|
|
|
assert.deepEqual(term_types, ["stream", "topic", "sender"]);
|
|
|
|
// test caching of term types
|
|
// init and stub
|
|
filter = new Filter(terms);
|
|
filter.stub = filter._build_sorted_term_types;
|
|
filter._build_sorted_term_types = function () {
|
|
this._build_sorted_term_types_called = true;
|
|
return this.stub();
|
|
};
|
|
|
|
// uncached trial
|
|
filter._build_sorted_term_types_called = false;
|
|
const built_terms = filter.sorted_term_types();
|
|
assert.deepEqual(built_terms, ["stream", "topic", "sender"]);
|
|
assert.ok(filter._build_sorted_term_types_called);
|
|
|
|
// cached trial
|
|
filter._build_sorted_term_types_called = false;
|
|
const cached_terms = filter.sorted_term_types();
|
|
assert.deepEqual(cached_terms, ["stream", "topic", "sender"]);
|
|
assert.ok(!filter._build_sorted_term_types_called);
|
|
});
|
|
|
|
test("first_valid_id_from", ({override}) => {
|
|
const terms = [{operator: "is", operand: "alerted"}];
|
|
|
|
const filter = new Filter(terms);
|
|
|
|
const messages = {
|
|
5: {id: 5, alerted: true},
|
|
10: {id: 10},
|
|
20: {id: 20, alerted: true},
|
|
30: {id: 30, type: "stream"},
|
|
40: {id: 40, alerted: false},
|
|
};
|
|
|
|
const msg_ids = [10, 20, 30, 40];
|
|
|
|
override(message_store, "get", (msg_id) => messages[msg_id]);
|
|
|
|
assert.equal(filter.first_valid_id_from([999]), undefined);
|
|
|
|
assert.equal(filter.first_valid_id_from(msg_ids), 20);
|
|
});
|
|
|
|
test("update_email", () => {
|
|
const terms = [
|
|
{operator: "dm", operand: "steve@foo.com"},
|
|
{operator: "sender", operand: "steve@foo.com"},
|
|
{operator: "stream", operand: "steve@foo.com"}, // try to be tricky
|
|
];
|
|
const filter = new Filter(terms);
|
|
filter.update_email(steve.user_id, "showell@foo.com");
|
|
assert.deepEqual(filter.operands("dm"), ["showell@foo.com"]);
|
|
assert.deepEqual(filter.operands("sender"), ["showell@foo.com"]);
|
|
assert.deepEqual(filter.operands("stream"), ["steve@foo.com"]);
|
|
});
|
|
|
|
function make_private_sub(name, stream_id) {
|
|
const sub = {
|
|
name,
|
|
stream_id,
|
|
invite_only: true,
|
|
};
|
|
stream_data.add_sub(sub);
|
|
}
|
|
|
|
function make_web_public_sub(name, stream_id) {
|
|
const sub = {
|
|
name,
|
|
stream_id,
|
|
is_web_public: true,
|
|
};
|
|
stream_data.add_sub(sub);
|
|
}
|
|
|
|
test("navbar_helpers", () => {
|
|
const stream_id = 43;
|
|
make_sub("Foo", stream_id);
|
|
|
|
// make sure title has names separated with correct delimiters
|
|
function properly_separated_names(names) {
|
|
const names_internationalized = new Intl.ListFormat("en", {
|
|
style: "long",
|
|
type: "conjunction",
|
|
}).format(names);
|
|
return names_internationalized;
|
|
}
|
|
|
|
function test_redirect_url_with_search(test_case) {
|
|
const terms = [...test_case.terms, {operator: "search", operand: "fizzbuzz"}];
|
|
const filter = new Filter(terms);
|
|
assert.equal(filter.generate_redirect_url(), test_case.redirect_url_with_search);
|
|
}
|
|
|
|
function test_common_narrow(test_case) {
|
|
const filter = new Filter(test_case.terms);
|
|
assert.equal(filter.is_common_narrow(), test_case.is_common_narrow);
|
|
}
|
|
|
|
function test_add_icon_data(test_case) {
|
|
const filter = new Filter(test_case.terms);
|
|
let context = {};
|
|
context = filter.add_icon_data(context);
|
|
assert.equal(context.icon, test_case.icon);
|
|
assert.equal(context.zulip_icon, test_case.zulip_icon);
|
|
}
|
|
|
|
function test_get_title(test_case) {
|
|
const filter = new Filter(test_case.terms);
|
|
assert.deepEqual(filter.get_title(), test_case.title);
|
|
}
|
|
|
|
function test_helpers(test_case) {
|
|
// debugging tip: add a `console.log(test_case)` here
|
|
test_common_narrow(test_case);
|
|
test_add_icon_data(test_case);
|
|
test_get_title(test_case);
|
|
test_redirect_url_with_search(test_case);
|
|
}
|
|
|
|
const sender = [{operator: "sender", operand: joe.email}];
|
|
const guest_sender = [{operator: "sender", operand: alice.email}];
|
|
const in_home = [{operator: "in", operand: "home"}];
|
|
const in_all = [{operator: "in", operand: "all"}];
|
|
const is_starred = [{operator: "is", operand: "starred"}];
|
|
const is_dm = [{operator: "is", operand: "dm"}];
|
|
const is_mentioned = [{operator: "is", operand: "mentioned"}];
|
|
const is_resolved = [{operator: "is", operand: "resolved"}];
|
|
const streams_public = [{operator: "streams", operand: "public"}];
|
|
const stream_topic_terms = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
];
|
|
// foo stream exists
|
|
const stream_term = [{operator: "stream", operand: "foo"}];
|
|
make_private_sub("psub", "22");
|
|
const private_stream_term = [{operator: "stream", operand: "psub"}];
|
|
make_web_public_sub("webPublicSub", "12"); // capitalized just to try be tricky and robust.
|
|
const web_public_stream = [{operator: "stream", operand: "webPublicSub"}];
|
|
const non_existent_stream = [{operator: "stream", operand: "Elephant"}];
|
|
const non_existent_stream_topic = [
|
|
{operator: "stream", operand: "Elephant"},
|
|
{operator: "topic", operand: "pink"},
|
|
];
|
|
const dm = [{operator: "dm", operand: "joe@example.com"}];
|
|
const dm_group = [{operator: "dm", operand: "joe@example.com,STEVE@foo.com"}];
|
|
const dm_with_guest = [{operator: "dm", operand: "alice@example.com"}];
|
|
const dm_group_including_guest = [
|
|
{operator: "dm", operand: "alice@example.com,joe@example.com"},
|
|
];
|
|
const dm_group_including_missing_person = [
|
|
{operator: "dm", operand: "joe@example.com,STEVE@foo.com,sally@doesnotexist.com"},
|
|
];
|
|
// not common narrows, but used for browser title updates
|
|
const is_alerted = [{operator: "is", operand: "alerted"}];
|
|
const is_unread = [{operator: "is", operand: "unread"}];
|
|
const stream_topic_near = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
{operator: "near", operand: "12"},
|
|
];
|
|
const dm_near = [
|
|
{operator: "dm", operand: "joe@example.com"},
|
|
{operator: "near", operand: "12"},
|
|
];
|
|
|
|
const test_cases = [
|
|
{
|
|
terms: sender,
|
|
is_common_narrow: true,
|
|
icon: undefined,
|
|
title: "translated: Messages sent by " + joe.full_name,
|
|
redirect_url_with_search: "/#narrow/sender/" + joe.user_id + "-joe",
|
|
},
|
|
{
|
|
terms: guest_sender,
|
|
is_common_narrow: true,
|
|
icon: undefined,
|
|
title: "translated: Messages sent by translated: alice (guest)",
|
|
redirect_url_with_search: "/#narrow/sender/" + alice.user_id + "-alice",
|
|
},
|
|
{
|
|
terms: is_starred,
|
|
is_common_narrow: true,
|
|
zulip_icon: "star-filled",
|
|
title: "translated: Starred messages",
|
|
redirect_url_with_search: "/#narrow/is/starred",
|
|
},
|
|
{
|
|
terms: in_home,
|
|
is_common_narrow: true,
|
|
icon: "home",
|
|
title: "translated: All messages",
|
|
redirect_url_with_search: "#",
|
|
},
|
|
{
|
|
terms: in_all,
|
|
is_common_narrow: true,
|
|
icon: "home",
|
|
title: "translated: All messages including muted streams",
|
|
redirect_url_with_search: "#",
|
|
},
|
|
{
|
|
terms: is_dm,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: "translated: All direct messages",
|
|
redirect_url_with_search: "/#narrow/is/dm",
|
|
},
|
|
{
|
|
terms: is_mentioned,
|
|
is_common_narrow: true,
|
|
zulip_icon: "at-sign",
|
|
title: "translated: Mentions",
|
|
redirect_url_with_search: "/#narrow/is/mentioned",
|
|
},
|
|
{
|
|
terms: is_resolved,
|
|
is_common_narrow: true,
|
|
icon: "check",
|
|
title: "translated: Topics marked as resolved",
|
|
redirect_url_with_search: "/#narrow/topics/is/resolved",
|
|
},
|
|
{
|
|
terms: stream_topic_terms,
|
|
is_common_narrow: true,
|
|
zulip_icon: "hashtag",
|
|
title: "Foo",
|
|
redirect_url_with_search: "/#narrow/stream/43-Foo/topic/bar",
|
|
},
|
|
{
|
|
terms: streams_public,
|
|
is_common_narrow: true,
|
|
icon: undefined,
|
|
title: "translated: Messages in all public streams",
|
|
redirect_url_with_search: "/#narrow/streams/public",
|
|
},
|
|
{
|
|
terms: stream_term,
|
|
is_common_narrow: true,
|
|
zulip_icon: "hashtag",
|
|
title: "Foo",
|
|
redirect_url_with_search: "/#narrow/stream/43-Foo",
|
|
},
|
|
{
|
|
terms: non_existent_stream,
|
|
is_common_narrow: true,
|
|
icon: "question-circle-o",
|
|
title: "translated: Unknown stream #Elephant",
|
|
redirect_url_with_search: "#",
|
|
},
|
|
{
|
|
terms: non_existent_stream_topic,
|
|
is_common_narrow: true,
|
|
icon: "question-circle-o",
|
|
title: "translated: Unknown stream #Elephant",
|
|
redirect_url_with_search: "#",
|
|
},
|
|
{
|
|
terms: private_stream_term,
|
|
is_common_narrow: true,
|
|
zulip_icon: "lock",
|
|
title: "psub",
|
|
redirect_url_with_search: "/#narrow/stream/22-psub",
|
|
},
|
|
{
|
|
terms: web_public_stream,
|
|
is_common_narrow: true,
|
|
zulip_icon: "globe",
|
|
title: "webPublicSub",
|
|
redirect_url_with_search: "/#narrow/stream/12-webPublicSub",
|
|
},
|
|
{
|
|
terms: dm,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: properly_separated_names([joe.full_name]),
|
|
redirect_url_with_search:
|
|
"/#narrow/dm/" + joe.user_id + "-" + parseOneAddress(joe.email).local,
|
|
},
|
|
{
|
|
terms: dm_group,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: properly_separated_names([joe.full_name, steve.full_name]),
|
|
redirect_url_with_search: "/#narrow/dm/" + joe.user_id + "," + steve.user_id + "-group",
|
|
},
|
|
{
|
|
terms: dm_with_guest,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: "translated: alice (guest)",
|
|
redirect_url_with_search:
|
|
"/#narrow/dm/" + alice.user_id + "-" + parseOneAddress(alice.email).local,
|
|
},
|
|
{
|
|
terms: dm_group_including_guest,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: "translated: alice (guest) and joe",
|
|
redirect_url_with_search: "/#narrow/dm/" + joe.user_id + "," + alice.user_id + "-group",
|
|
},
|
|
{
|
|
terms: dm_group_including_missing_person,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: properly_separated_names([
|
|
joe.full_name,
|
|
steve.full_name,
|
|
"sally@doesnotexist.com",
|
|
]),
|
|
redirect_url_with_search: "/#narrow/dm/undefined",
|
|
},
|
|
{
|
|
terms: is_alerted,
|
|
is_common_narrow: false,
|
|
icon: undefined,
|
|
title: "translated: Alerted messages",
|
|
redirect_url_with_search: "#",
|
|
},
|
|
{
|
|
terms: is_unread,
|
|
is_common_narrow: false,
|
|
icon: undefined,
|
|
title: "translated: Unread messages",
|
|
redirect_url_with_search: "#",
|
|
},
|
|
{
|
|
terms: stream_topic_near,
|
|
is_common_narrow: false,
|
|
zulip_icon: "hashtag",
|
|
title: "Foo",
|
|
redirect_url_with_search: "#",
|
|
},
|
|
{
|
|
terms: dm_near,
|
|
is_common_narrow: false,
|
|
icon: "envelope",
|
|
title: properly_separated_names([joe.full_name]),
|
|
redirect_url_with_search: "#",
|
|
},
|
|
];
|
|
|
|
realm.realm_enable_guest_user_indicator = true;
|
|
|
|
for (const test_case of test_cases) {
|
|
test_helpers(test_case);
|
|
}
|
|
|
|
// TODO: these may be removed, based on design decisions
|
|
const sender_me = [{operator: "sender", operand: "me"}];
|
|
const sender_joe = [{operator: "sender", operand: joe.email}];
|
|
|
|
const redirect_edge_cases = [
|
|
{
|
|
terms: sender_me,
|
|
redirect_url_with_search: "/#narrow/sender/" + me.user_id + "-Me-Myself",
|
|
is_common_narrow: true,
|
|
},
|
|
{
|
|
terms: sender_joe,
|
|
redirect_url_with_search: "/#narrow/sender/" + joe.user_id + "-joe",
|
|
is_common_narrow: true,
|
|
},
|
|
];
|
|
|
|
for (const test_case of redirect_edge_cases) {
|
|
test_redirect_url_with_search(test_case);
|
|
}
|
|
|
|
// TODO: test every single one of the "ALL" redirects from the navbar behaviour table
|
|
|
|
// incomplete and weak test cases just to restore coverage of filter.ts
|
|
const complex_term = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
{operator: "sender", operand: "me"},
|
|
];
|
|
|
|
const redirect_url = "#";
|
|
|
|
let filter = new Filter(complex_term);
|
|
assert.equal(filter.generate_redirect_url(), redirect_url);
|
|
assert.equal(filter.is_common_narrow(), false);
|
|
|
|
const stream_topic_search_term = [
|
|
{operator: "stream", operand: "foo"},
|
|
{operator: "topic", operand: "bar"},
|
|
{operator: "search", operand: "potato"},
|
|
];
|
|
|
|
const stream_topic_search_term_test_case = {
|
|
terms: stream_topic_search_term,
|
|
title: undefined,
|
|
};
|
|
|
|
test_get_title(stream_topic_search_term_test_case);
|
|
|
|
realm.realm_enable_guest_user_indicator = false;
|
|
const guest_user_test_cases_without_indicator = [
|
|
{
|
|
terms: guest_sender,
|
|
is_common_narrow: true,
|
|
icon: undefined,
|
|
title: "translated: Messages sent by alice",
|
|
redirect_url_with_search: "/#narrow/sender/" + alice.user_id + "-alice",
|
|
},
|
|
{
|
|
terms: dm_with_guest,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: properly_separated_names([alice.full_name]),
|
|
redirect_url_with_search:
|
|
"/#narrow/dm/" + alice.user_id + "-" + parseOneAddress(alice.email).local,
|
|
},
|
|
{
|
|
terms: dm_group_including_guest,
|
|
is_common_narrow: true,
|
|
icon: "envelope",
|
|
title: properly_separated_names([alice.full_name, joe.full_name]),
|
|
redirect_url_with_search: "/#narrow/dm/" + joe.user_id + "," + alice.user_id + "-group",
|
|
},
|
|
];
|
|
|
|
for (const test_case of guest_user_test_cases_without_indicator) {
|
|
test_helpers(test_case);
|
|
}
|
|
|
|
// this is actually wrong, but the code is currently not robust enough to throw an error here
|
|
// also, used as an example of triggering last return statement.
|
|
const default_redirect = {
|
|
terms: [{operator: "stream", operand: "foo"}],
|
|
redirect_url: "#",
|
|
};
|
|
|
|
filter = new Filter(default_redirect.terms);
|
|
assert.equal(filter.generate_redirect_url(), default_redirect.redirect_url);
|
|
});
|
|
|
|
test("error_cases", () => {
|
|
// This test just gives us 100% line coverage on defensive code that
|
|
// should not be reached unless we break other code.
|
|
|
|
const predicate = get_predicate([["dm", "Joe@example.com"]]);
|
|
blueslip.expect("error", "Empty recipient list in message");
|
|
assert.ok(!predicate({type: "private", display_recipient: []}));
|
|
});
|
|
|
|
run_test("is_spectator_compatible", () => {
|
|
// tests same as test_is_spectator_compatible from test_message_fetch.py
|
|
assert.ok(Filter.is_spectator_compatible([]));
|
|
assert.ok(Filter.is_spectator_compatible([{operator: "has", operand: "attachment"}]));
|
|
assert.ok(Filter.is_spectator_compatible([{operator: "has", operand: "image"}]));
|
|
assert.ok(Filter.is_spectator_compatible([{operator: "search", operand: "magic"}]));
|
|
assert.ok(Filter.is_spectator_compatible([{operator: "near", operand: "15"}]));
|
|
assert.ok(
|
|
Filter.is_spectator_compatible([
|
|
{operator: "id", operand: "15"},
|
|
{operator: "has", operand: "attachment"},
|
|
]),
|
|
);
|
|
assert.ok(Filter.is_spectator_compatible([{operator: "sender", operand: "hamlet@zulip.com"}]));
|
|
assert.ok(!Filter.is_spectator_compatible([{operator: "dm", operand: "hamlet@zulip.com"}]));
|
|
assert.ok(
|
|
!Filter.is_spectator_compatible([{operator: "dm-including", operand: "hamlet@zulip.com"}]),
|
|
);
|
|
assert.ok(Filter.is_spectator_compatible([{operator: "stream", operand: "Denmark"}]));
|
|
assert.ok(
|
|
Filter.is_spectator_compatible([
|
|
{operator: "stream", operand: "Denmark"},
|
|
{operator: "topic", operand: "logic"},
|
|
]),
|
|
);
|
|
assert.ok(!Filter.is_spectator_compatible([{operator: "is", operand: "starred"}]));
|
|
assert.ok(!Filter.is_spectator_compatible([{operator: "is", operand: "dm"}]));
|
|
assert.ok(Filter.is_spectator_compatible([{operator: "streams", operand: "public"}]));
|
|
|
|
// Malformed input not allowed
|
|
assert.ok(!Filter.is_spectator_compatible([{operator: "has"}]));
|
|
|
|
// "is:private" was renamed to "is:dm"
|
|
assert.ok(!Filter.is_spectator_compatible([{operator: "is", operand: "private"}]));
|
|
// "pm-with" was renamed to "dm"
|
|
assert.ok(
|
|
!Filter.is_spectator_compatible([{operator: "pm-with", operand: "hamlet@zulip.com"}]),
|
|
);
|
|
// "group-pm-with:" was replaced with "dm-including:"
|
|
assert.ok(
|
|
!Filter.is_spectator_compatible([{operator: "group-pm-with", operand: "hamlet@zulip.com"}]),
|
|
);
|
|
});
|
|
|
|
run_test("is_in_home", () => {
|
|
const filter = new Filter([{operator: "in", operand: "home"}]);
|
|
assert.ok(filter.is_in_home());
|
|
|
|
const filter2 = new Filter([{operator: "in", operand: "all"}]);
|
|
assert.ok(!filter2.is_in_home());
|
|
|
|
// Test home with additional terms is not all messages.
|
|
const filter3 = new Filter([
|
|
{operator: "in", operand: "home"},
|
|
{operator: "topic", operand: "foo"},
|
|
]);
|
|
assert.ok(!filter3.is_in_home());
|
|
});
|