"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} = 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", }; people.add_active_user(me); people.add_active_user(joe); people.add_active_user(steve); people.initialize_current_user(me.user_id); function assert_same_operators(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(operators) { operators = operators.map((op) => ({ operator: op[0], operand: op[1], })); return new Filter(operators).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 operators = [ {operator: "stream", operand: "foo"}, {operator: "stream", operand: "exclude_stream", negated: true}, {operator: "topic", operand: "bar"}, ]; let filter = new Filter(operators); assert_same_operators(filter.operators(), operators); 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_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()); operators = [ {operator: "stream", operand: "foo"}, {operator: "topic", operand: "bar"}, {operator: "search", operand: "pizza"}, ]; filter = new Filter(operators); assert.ok(filter.is_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()); operators = [ {operator: "stream", operand: "foo"}, {operator: "topic", operand: "bar"}, {operator: "near", operand: 17}, ]; filter = new Filter(operators); assert.ok(!filter.is_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. operators = [{operator: "stream", operand: "exclude", negated: true}]; filter = new Filter(operators); 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. operators = [{operator: "search", operand: "stop_word", negated: true}]; filter = new Filter(operators); 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. operators = [{operator: "has", operand: "images", negated: true}]; filter = new Filter(operators); 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()); operators = [{operator: "streams", operand: "public", negated: true}]; filter = new Filter(operators); 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()); operators = [{operator: "streams", operand: "public"}]; filter = new Filter(operators); 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()); operators = [{operator: "is", operand: "dm"}]; filter = new Filter(operators); 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" operators = [{operator: "is", operand: "private"}]; filter = new Filter(operators); assert.ok(filter.has_operand("is", "dm")); assert.ok(!filter.has_operand("is", "private")); operators = [{operator: "is", operand: "mentioned"}]; filter = new Filter(operators); 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()); operators = [{operator: "is", operand: "starred"}]; filter = new Filter(operators); 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()); operators = [{operator: "dm", operand: "joe@example.com"}]; filter = new Filter(operators); 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()); operators = [{operator: "dm", operand: "joe@example.com,jack@example.com"}]; filter = new Filter(operators); 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" operators = [{operator: "pm-with", operand: "joe@example.com"}]; filter = new Filter(operators); assert.ok(filter.has_operator("dm")); assert.ok(!filter.has_operator(" pm-with")); operators = [{operator: "dm-including", operand: "joe@example.com"}]; filter = new Filter(operators); 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" operators = [{operator: "group-pm-with", operand: "joe@example.com"}]; filter = new Filter(operators); assert.ok(filter.has_operator("dm-including")); assert.ok(!filter.has_operator("group-pm-with")); operators = [{operator: "is", operand: "resolved"}]; filter = new Filter(operators); 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. operators = [ {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(operators); 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()); operators = [ {operator: "stream", operand: "foo"}, {operator: "topic", operand: "bar"}, ]; filter = new Filter(operators); assert.ok(!filter.is_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_operators_to_test) { additional_operators_to_test = additional_operators_to_test || []; let has_operator = [{operator: "has", operand: "link"}]; let filter = new Filter([...additional_operators_to_test, ...has_operator]); assert.ok(!filter.can_mark_messages_read()); has_operator = [{operator: "has", operand: "link", negated: true}]; filter = new Filter([...additional_operators_to_test, ...has_operator]); assert.ok(!filter.can_mark_messages_read()); has_operator = [{operator: "has", operand: "image"}]; filter = new Filter([...additional_operators_to_test, ...has_operator]); assert.ok(!filter.can_mark_messages_read()); has_operator = [{operator: "has", operand: "image", negated: true}]; filter = new Filter([...additional_operators_to_test, ...has_operator]); assert.ok(!filter.can_mark_messages_read()); has_operator = [{operator: "has", operand: "attachment", negated: true}]; filter = new Filter([...additional_operators_to_test, ...has_operator]); assert.ok(!filter.can_mark_messages_read()); has_operator = [{operator: "has", operand: "attachment"}]; filter = new Filter([...additional_operators_to_test, ...has_operator]); assert.ok(!filter.can_mark_messages_read()); } function assert_not_mark_read_with_is_operands(additional_operators_to_test) { additional_operators_to_test = additional_operators_to_test || []; let is_operator = [{operator: "is", operand: "starred"}]; let filter = new Filter([...additional_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); is_operator = [{operator: "is", operand: "starred", negated: true}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); is_operator = [{operator: "is", operand: "mentioned"}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); if (additional_operators_to_test.length === 0) { assert.ok(filter.can_mark_messages_read()); } else { assert.ok(!filter.can_mark_messages_read()); } is_operator = [{operator: "is", operand: "mentioned", negated: true}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); is_operator = [{operator: "is", operand: "alerted"}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); is_operator = [{operator: "is", operand: "alerted", negated: true}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); is_operator = [{operator: "is", operand: "unread"}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); is_operator = [{operator: "is", operand: "unread", negated: true}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); is_operator = [{operator: "is", operand: "resolved"}]; filter = new Filter([...additional_operators_to_test, ...is_operator]); if (additional_operators_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_operators_to_test, ...is_operator]); assert.ok(!filter.can_mark_messages_read()); } function assert_not_mark_read_when_searching(additional_operators_to_test) { additional_operators_to_test = additional_operators_to_test || []; let search_op = [{operator: "search", operand: "keyword"}]; let filter = new Filter([...additional_operators_to_test, ...search_op]); assert.ok(!filter.can_mark_messages_read()); search_op = [{operator: "search", operand: "keyword", negated: true}]; filter = new Filter([...additional_operators_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_operator = [{operator: "stream", operand: "foo"}]; let filter = new Filter(stream_operator); assert.ok(filter.can_mark_messages_read()); assert_not_mark_read_with_has_operands(stream_operator); assert_not_mark_read_with_is_operands(stream_operator); assert_not_mark_read_when_searching(stream_operator); 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_operators = [ {operator: "stream", operand: "foo"}, {operator: "topic", operand: "bar"}, ]; filter = new Filter(stream_topic_operators); assert.ok(filter.can_mark_messages_read()); assert_not_mark_read_with_has_operands(stream_topic_operators); assert_not_mark_read_with_is_operands(stream_topic_operators); assert_not_mark_read_when_searching(stream_topic_operators); const stream_negated_topic_operators = [ {operator: "stream", operand: "foo"}, {operator: "topic", operand: "bar", negated: true}, ]; filter = new Filter(stream_negated_topic_operators); 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 operators = [{operator: "is", operand: "any"}]; let filter = new Filter(operators); assert.ok(filter.allow_use_first_unread_when_narrowing()); operators = [{operator: "search", operand: "query to search"}]; filter = new Filter(operators); 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 operators = [{operator: "is", operand: "any"}]; filter = new Filter(operators); assert.ok(!filter.can_mark_messages_read()); assert.ok(filter.allow_use_first_unread_when_narrowing()); }); test("filter_with_new_params_topic", () => { const operators = [ {operator: "stream", operand: "foo"}, {operator: "topic", operand: "old topic"}, ]; const filter = new Filter(operators); 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 operators = [ {operator: "stream", operand: "foo"}, {operator: "topic", operand: "old topic"}, ]; const filter = new Filter(operators); 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_operators", () => { const term = { operator: "stream", operand: "foo", }; const operators = [term]; const filter = new Filter(operators); assert.deepEqual(filter.operands("stream"), ["foo"]); assert.ok(filter.can_bucket_by("stream")); }); test("public_operators", ({override}) => { stream_data.clear_subscriptions(); let operators = [ {operator: "stream", operand: "some_stream"}, {operator: "in", operand: "all"}, {operator: "topic", operand: "bar"}, ]; let filter = new Filter(operators); override(page_params, "narrow_stream", undefined); assert_same_operators(filter.public_operators(), operators); assert.ok(filter.can_bucket_by("stream")); operators = [{operator: "stream", operand: "default"}]; filter = new Filter(operators); override(page_params, "narrow_stream", "default"); assert_same_operators(filter.public_operators(), []); }); 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"})); 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. predicate = get_predicate([ ["stream", "old-Stream"], ["topic", "Bar"], ]); assert.ok(predicate({type: "stream", stream: "Old-stream", topic: "bar"})); assert.ok(!predicate({type: "stream", stream: "no-match", 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"})); with_overrides(({override}) => { override(page_params, "narrow_stream", "kiosk"); assert.ok(predicate({stream: "kiosk"})); }); 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: '
', }; const link_msg = { content: '', }; const non_img_attachment_msg = { content: '', }; const no_has_filter_matching_msg = { content: "Testing
", }; 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) { $(`