notifications: Split out message_notifications module.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2023-10-06 13:29:25 -07:00 committed by Tim Abbott
parent e48771993b
commit db20fd12e0
8 changed files with 472 additions and 459 deletions

View File

@ -133,6 +133,7 @@ EXEMPT_FILES = make_set(
"web/src/message_list_view.js",
"web/src/message_lists.js",
"web/src/message_live_update.js",
"web/src/message_notifications.js",
"web/src/message_scroll.js",
"web/src/message_scroll_state.ts",
"web/src/message_util.js",

View File

@ -13,6 +13,7 @@ import * as message_edit from "./message_edit";
import * as message_edit_history from "./message_edit_history";
import * as message_helper from "./message_helper";
import * as message_lists from "./message_lists";
import * as message_notifications from "./message_notifications";
import * as message_store from "./message_store";
import * as message_util from "./message_util";
import * as narrow from "./narrow";
@ -161,7 +162,7 @@ export function insert_new_messages(messages, sent_by_this_client) {
}
unread_ops.process_visible();
notifications.received_messages(messages);
message_notifications.received_messages(messages);
stream_list.update_streams_sidebar();
pm_list.update_private_messages();
recent_view_ui.process_messages(messages);
@ -482,7 +483,7 @@ export function update_messages(events) {
anchor_message.last_edit_timestamp = event.edit_timestamp;
}
notifications.received_messages([anchor_message]);
message_notifications.received_messages([anchor_message]);
alert_words.process_message(anchor_message);
}

View File

@ -0,0 +1,404 @@
import $ from "jquery";
import * as alert_words from "./alert_words";
import * as blueslip from "./blueslip";
import {$t} from "./i18n";
import * as message_parser from "./message_parser";
import * as narrow from "./narrow";
import * as notifications from "./notifications";
import * as people from "./people";
import * as spoilers from "./spoilers";
import * as stream_data from "./stream_data";
import * as ui_util from "./ui_util";
import * as unread from "./unread";
import {user_settings} from "./user_settings";
import * as user_topics from "./user_topics";
function get_notification_content(message) {
let content;
// Convert the content to plain text, replacing emoji with their alt text
const $content = $("<div>").html(message.content);
ui_util.replace_emoji_with_text($content);
spoilers.hide_spoilers_in_notification($content);
if (
$content.text().trim() === "" &&
(message_parser.message_has_image(message) ||
message_parser.message_has_attachment(message))
) {
content = $t({defaultMessage: "(attached file)"});
} else {
content = $content.text();
}
if (message.is_me_message) {
content = message.sender_full_name + content.slice(3);
}
if (
(message.type === "private" || message.type === "test-notification") &&
!user_settings.pm_content_in_desktop_notifications
) {
content = "New direct message from " + message.sender_full_name;
}
if (content.length > 150) {
let i;
// Truncate content at a word boundary
for (i = 150; i > 0; i -= 1) {
if (content[i] === " ") {
break;
}
}
content = content.slice(0, i);
content += " [...]";
}
return content;
}
function debug_notification_source_value(message) {
let notification_source;
if (message.type === "private" || message.type === "test-notification") {
notification_source = "pm";
} else if (message.mentioned) {
notification_source = "mention";
} else if (message.alerted) {
notification_source = "alert";
} else {
notification_source = "stream";
}
blueslip.debug("Desktop notification from source " + notification_source);
}
function get_notification_key(message) {
let key;
if (message.type === "private" || message.type === "test-notification") {
key = message.display_reply_to;
} else {
const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
key = message.sender_full_name + " to " + stream_name + " > " + message.topic;
}
return key;
}
function remove_sender_from_list_of_recipients(message) {
return `, ${message.display_reply_to}, `
.replace(`, ${message.sender_full_name}, `, ", ")
.slice(", ".length, -", ".length);
}
function get_notification_title(message, content, msg_count) {
let title = message.sender_full_name;
let other_recipients;
if (msg_count > 1) {
title = msg_count + " messages from " + title;
}
switch (message.type) {
case "test-notification":
other_recipients = remove_sender_from_list_of_recipients(message);
break;
case "private":
other_recipients = remove_sender_from_list_of_recipients(message);
if (message.display_recipient.length > 2) {
// If the message has too many recipients to list them all...
if (content.length + title.length + other_recipients.length > 230) {
// Then count how many people are in the conversation and summarize
// by saying the conversation is with "you and [number] other people"
other_recipients =
other_recipients.replaceAll(/[^,]/g, "").length + " other people";
}
title += " (to you and " + other_recipients + ")";
} else {
title += " (to you)";
}
break;
case "stream": {
const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
title += " (to " + stream_name + " > " + message.topic + ")";
break;
}
}
return title;
}
export function process_notification(notification) {
const message = notification.message;
const content = get_notification_content(message);
const key = get_notification_key(message);
let notification_object;
let msg_count = 1;
debug_notification_source_value(message);
if (notifications.notice_memory.has(key)) {
msg_count = notifications.notice_memory.get(key).msg_count + 1;
notification_object = notifications.notice_memory.get(key).obj;
notification_object.close();
}
const title = get_notification_title(message, content, msg_count);
if (notification.desktop_notify) {
const icon_url = people.small_avatar_url(message);
notification_object = new notifications.NotificationAPI(title, {
icon: icon_url,
body: content,
tag: message.id,
});
notifications.notice_memory.set(key, {
obj: notification_object,
msg_count,
message_id: message.id,
});
if (typeof notification_object.addEventListener === "function") {
// Sadly, some third-party Electron apps like Franz/Ferdi
// misimplement the Notification API not inheriting from
// EventTarget. This results in addEventListener being
// unavailable for them.
notification_object.addEventListener("click", () => {
notification_object.close();
if (message.type !== "test-notification") {
narrow.by_topic(message.id, {trigger: "notification"});
}
window.focus();
});
notification_object.addEventListener("close", () => {
notifications.notice_memory.delete(key);
});
}
}
}
export function message_is_notifiable(message) {
// Independent of the user's notification settings, are there
// properties of the message that unconditionally mean we
// shouldn't notify about it.
if (message.sent_by_me) {
return false;
}
// If a message is edited multiple times, we want to err on the side of
// not spamming notifications.
if (message.notification_sent) {
return false;
}
// @-<username> mentions take precedence over muted-ness. Note
// that @all mentions are still suppressed by muting.
if (message.mentioned_me_directly) {
return true;
}
// Messages to followed topics take precedence over muted-ness.
if (
message.type === "stream" &&
user_topics.is_topic_followed(message.stream_id, message.topic)
) {
return true;
}
// Messages to unmuted topics in muted streams may generate desktop notifications.
if (
message.type === "stream" &&
stream_data.is_muted(message.stream_id) &&
!user_topics.is_topic_unmuted(message.stream_id, message.topic)
) {
return false;
}
if (message.type === "stream" && user_topics.is_topic_muted(message.stream_id, message.topic)) {
return false;
}
// Everything else is on the table; next filter based on notification
// settings.
return true;
}
export function should_send_desktop_notification(message) {
// Always notify for testing notifications.
if (message.type === "test-notification") {
return true;
}
// For streams, send if desktop notifications are enabled for all
// message on this stream.
if (
message.type === "stream" &&
stream_data.receives_notifications(message.stream_id, "desktop_notifications")
) {
return true;
}
// enable_followed_topic_desktop_notifications determines whether we pop up
// a notification for messages in followed topics.
if (
message.type === "stream" &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_desktop_notifications
) {
return true;
}
// enable_desktop_notifications determines whether we pop up a
// notification for direct messages, mentions, and/or alerts.
if (!user_settings.enable_desktop_notifications) {
return false;
}
// And then we need to check if the message is a direct message,
// mention, wildcard mention with wildcard_mentions_notify, or alert.
if (message.type === "private") {
return true;
}
if (alert_words.notifies(message)) {
return true;
}
if (message.mentioned_me_directly) {
return true;
}
// The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
// should be placed below (as they are right now) the 'user_settings.enable_desktop_notifications'
// block because the global, stream-specific, and followed topic wildcard mention
// settings are wrappers around the personal-mention setting.
// wildcard mentions
if (
message.mentioned &&
stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
) {
return true;
}
// Followed topic wildcard mentions
if (
message.mentioned &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_wildcard_mentions_notify
) {
return true;
}
return false;
}
export function should_send_audible_notification(message) {
// If `None` is selected as the notification sound, never send
// audible notifications regardless of other configuration.
if (user_settings.notification_sound === "none") {
return false;
}
// For streams, ding if sounds are enabled for all messages on
// this stream.
if (
message.type === "stream" &&
stream_data.receives_notifications(message.stream_id, "audible_notifications")
) {
return true;
}
// enable_followed_topic_audible_notifications determines whether we ding
// for messages in followed topics.
if (
message.type === "stream" &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_audible_notifications
) {
return true;
}
// enable_sounds determines whether we ding for direct messages,
// mentions, and/or alerts.
if (!user_settings.enable_sounds) {
return false;
}
// And then we need to check if the message is a direct message,
// mention, wildcard mention with wildcard_mentions_notify, or alert.
if (message.type === "private" || message.type === "test-notification") {
return true;
}
if (alert_words.notifies(message)) {
return true;
}
if (message.mentioned_me_directly) {
return true;
}
// The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
// should be placed below (as they are right now) the 'user_settings.enable_sounds'
// block because the global, stream-specific, and followed topic wildcard mention
// settings are wrappers around the personal-mention setting.
// wildcard mentions
if (
message.mentioned &&
stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
) {
return true;
}
// Followed topic wildcard mentions
if (
message.mentioned &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_wildcard_mentions_notify
) {
return true;
}
return false;
}
export function received_messages(messages) {
for (const message of messages) {
if (!message_is_notifiable(message)) {
continue;
}
if (!unread.message_unread(message)) {
// The message is already read; Zulip is currently in focus.
continue;
}
message.notification_sent = true;
if (should_send_desktop_notification(message)) {
process_notification({
message,
desktop_notify: notifications.granted_desktop_notifications_permission(),
});
}
if (should_send_audible_notification(message)) {
ui_util.play_audio($("#user-notification-sound-audio")[0]);
}
}
}
export function send_test_notification(content) {
received_messages([
{
id: Math.random(),
type: "test-notification",
sender_email: "notification-bot@zulip.com",
sender_full_name: "Notification Bot",
display_reply_to: "Notification Bot",
content,
unread: true,
},
]);
}

View File

@ -3,26 +3,21 @@ import $ from "jquery";
import render_message_sent_banner from "../templates/compose_banner/message_sent_banner.hbs";
import render_unmute_topic_banner from "../templates/compose_banner/unmute_topic_banner.hbs";
import * as alert_words from "./alert_words";
import * as blueslip from "./blueslip";
import * as compose_banner from "./compose_banner";
import * as hash_util from "./hash_util";
import {$t} from "./i18n";
import * as message_lists from "./message_lists";
import * as message_parser from "./message_parser";
import * as narrow from "./narrow";
import * as narrow_state from "./narrow_state";
import * as people from "./people";
import * as spoilers from "./spoilers";
import * as stream_data from "./stream_data";
import * as ui_util from "./ui_util";
import * as unread from "./unread";
import {user_settings} from "./user_settings";
import * as user_topics from "./user_topics";
const notice_memory = new Map();
export const notice_memory = new Map();
let NotificationAPI;
export let NotificationAPI;
export function set_notification_api(n) {
NotificationAPI = n;
@ -140,171 +135,6 @@ export function notify_above_composebox(
compose_banner.append_compose_banner_to_banner_list($notification, $("#compose_banners"));
}
function get_notification_content(message) {
let content;
// Convert the content to plain text, replacing emoji with their alt text
const $content = $("<div>").html(message.content);
ui_util.replace_emoji_with_text($content);
spoilers.hide_spoilers_in_notification($content);
if (
$content.text().trim() === "" &&
(message_parser.message_has_image(message) ||
message_parser.message_has_attachment(message))
) {
content = $t({defaultMessage: "(attached file)"});
} else {
content = $content.text();
}
if (message.is_me_message) {
content = message.sender_full_name + content.slice(3);
}
if (
(message.type === "private" || message.type === "test-notification") &&
!user_settings.pm_content_in_desktop_notifications
) {
content = "New direct message from " + message.sender_full_name;
}
if (content.length > 150) {
let i;
// Truncate content at a word boundary
for (i = 150; i > 0; i -= 1) {
if (content[i] === " ") {
break;
}
}
content = content.slice(0, i);
content += " [...]";
}
return content;
}
function debug_notification_source_value(message) {
let notification_source;
if (message.type === "private" || message.type === "test-notification") {
notification_source = "pm";
} else if (message.mentioned) {
notification_source = "mention";
} else if (message.alerted) {
notification_source = "alert";
} else {
notification_source = "stream";
}
blueslip.debug("Desktop notification from source " + notification_source);
}
function get_notification_key(message) {
let key;
if (message.type === "private" || message.type === "test-notification") {
key = message.display_reply_to;
} else {
const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
key = message.sender_full_name + " to " + stream_name + " > " + message.topic;
}
return key;
}
function remove_sender_from_list_of_recipients(message) {
return `, ${message.display_reply_to}, `
.replace(`, ${message.sender_full_name}, `, ", ")
.slice(", ".length, -", ".length);
}
function get_notification_title(message, content, msg_count) {
let title = message.sender_full_name;
let other_recipients;
if (msg_count > 1) {
title = msg_count + " messages from " + title;
}
switch (message.type) {
case "test-notification":
other_recipients = remove_sender_from_list_of_recipients(message);
break;
case "private":
other_recipients = remove_sender_from_list_of_recipients(message);
if (message.display_recipient.length > 2) {
// If the message has too many recipients to list them all...
if (content.length + title.length + other_recipients.length > 230) {
// Then count how many people are in the conversation and summarize
// by saying the conversation is with "you and [number] other people"
other_recipients =
other_recipients.replaceAll(/[^,]/g, "").length + " other people";
}
title += " (to you and " + other_recipients + ")";
} else {
title += " (to you)";
}
break;
case "stream": {
const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
title += " (to " + stream_name + " > " + message.topic + ")";
break;
}
}
return title;
}
export function process_notification(notification) {
const message = notification.message;
const content = get_notification_content(message);
const key = get_notification_key(message);
let notification_object;
let msg_count = 1;
debug_notification_source_value(message);
if (notice_memory.has(key)) {
msg_count = notice_memory.get(key).msg_count + 1;
notification_object = notice_memory.get(key).obj;
notification_object.close();
}
const title = get_notification_title(message, content, msg_count);
if (notification.desktop_notify) {
const icon_url = people.small_avatar_url(message);
notification_object = new NotificationAPI(title, {
icon: icon_url,
body: content,
tag: message.id,
});
notice_memory.set(key, {
obj: notification_object,
msg_count,
message_id: message.id,
});
if (typeof notification_object.addEventListener === "function") {
// Sadly, some third-party Electron apps like Franz/Ferdi
// misimplement the Notification API not inheriting from
// EventTarget. This results in addEventListener being
// unavailable for them.
notification_object.addEventListener("click", () => {
notification_object.close();
if (message.type !== "test-notification") {
narrow.by_topic(message.id, {trigger: "notification"});
}
window.focus();
});
notification_object.addEventListener("close", () => {
notice_memory.delete(key);
});
}
}
}
export function close_notification(message) {
for (const [key, notice_mem_entry] of notice_memory) {
if (notice_mem_entry.message_id === message.id) {
@ -314,192 +144,6 @@ export function close_notification(message) {
}
}
export function message_is_notifiable(message) {
// Independent of the user's notification settings, are there
// properties of the message that unconditionally mean we
// shouldn't notify about it.
if (message.sent_by_me) {
return false;
}
// If a message is edited multiple times, we want to err on the side of
// not spamming notifications.
if (message.notification_sent) {
return false;
}
// @-<username> mentions take precedence over muted-ness. Note
// that @all mentions are still suppressed by muting.
if (message.mentioned_me_directly) {
return true;
}
// Messages to followed topics take precedence over muted-ness.
if (
message.type === "stream" &&
user_topics.is_topic_followed(message.stream_id, message.topic)
) {
return true;
}
// Messages to unmuted topics in muted streams may generate desktop notifications.
if (
message.type === "stream" &&
stream_data.is_muted(message.stream_id) &&
!user_topics.is_topic_unmuted(message.stream_id, message.topic)
) {
return false;
}
if (message.type === "stream" && user_topics.is_topic_muted(message.stream_id, message.topic)) {
return false;
}
// Everything else is on the table; next filter based on notification
// settings.
return true;
}
export function should_send_desktop_notification(message) {
// Always notify for testing notifications.
if (message.type === "test-notification") {
return true;
}
// For streams, send if desktop notifications are enabled for all
// message on this stream.
if (
message.type === "stream" &&
stream_data.receives_notifications(message.stream_id, "desktop_notifications")
) {
return true;
}
// enable_followed_topic_desktop_notifications determines whether we pop up
// a notification for messages in followed topics.
if (
message.type === "stream" &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_desktop_notifications
) {
return true;
}
// enable_desktop_notifications determines whether we pop up a
// notification for direct messages, mentions, and/or alerts.
if (!user_settings.enable_desktop_notifications) {
return false;
}
// And then we need to check if the message is a direct message,
// mention, wildcard mention with wildcard_mentions_notify, or alert.
if (message.type === "private") {
return true;
}
if (alert_words.notifies(message)) {
return true;
}
if (message.mentioned_me_directly) {
return true;
}
// The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
// should be placed below (as they are right now) the 'user_settings.enable_desktop_notifications'
// block because the global, stream-specific, and followed topic wildcard mention
// settings are wrappers around the personal-mention setting.
// wildcard mentions
if (
message.mentioned &&
stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
) {
return true;
}
// Followed topic wildcard mentions
if (
message.mentioned &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_wildcard_mentions_notify
) {
return true;
}
return false;
}
export function should_send_audible_notification(message) {
// If `None` is selected as the notification sound, never send
// audible notifications regardless of other configuration.
if (user_settings.notification_sound === "none") {
return false;
}
// For streams, ding if sounds are enabled for all messages on
// this stream.
if (
message.type === "stream" &&
stream_data.receives_notifications(message.stream_id, "audible_notifications")
) {
return true;
}
// enable_followed_topic_audible_notifications determines whether we ding
// for messages in followed topics.
if (
message.type === "stream" &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_audible_notifications
) {
return true;
}
// enable_sounds determines whether we ding for direct messages,
// mentions, and/or alerts.
if (!user_settings.enable_sounds) {
return false;
}
// And then we need to check if the message is a direct message,
// mention, wildcard mention with wildcard_mentions_notify, or alert.
if (message.type === "private" || message.type === "test-notification") {
return true;
}
if (alert_words.notifies(message)) {
return true;
}
if (message.mentioned_me_directly) {
return true;
}
// The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
// should be placed below (as they are right now) the 'user_settings.enable_sounds'
// block because the global, stream-specific, and followed topic wildcard mention
// settings are wrappers around the personal-mention setting.
// wildcard mentions
if (
message.mentioned &&
stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
) {
return true;
}
// Followed topic wildcard mentions
if (
message.mentioned &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.enable_followed_topic_wildcard_mentions_notify
) {
return true;
}
return false;
}
export function granted_desktop_notifications_permission() {
return NotificationAPI && NotificationAPI.permission === "granted";
}
@ -510,44 +154,6 @@ export function request_desktop_notifications_permission() {
}
}
export function received_messages(messages) {
for (const message of messages) {
if (!message_is_notifiable(message)) {
continue;
}
if (!unread.message_unread(message)) {
// The message is already read; Zulip is currently in focus.
continue;
}
message.notification_sent = true;
if (should_send_desktop_notification(message)) {
process_notification({
message,
desktop_notify: granted_desktop_notifications_permission(),
});
}
if (should_send_audible_notification(message)) {
ui_util.play_audio($("#user-notification-sound-audio")[0]);
}
}
}
export function send_test_notification(content) {
received_messages([
{
id: Math.random(),
type: "test-notification",
sender_email: "notification-bot@zulip.com",
sender_full_name: "Notification Bot",
display_reply_to: "Notification Bot",
content,
unread: true,
},
]);
}
// Note that this returns values that are not HTML-escaped, for use in
// Handlebars templates that will do further escaping.
function get_message_header(message) {
@ -736,13 +342,13 @@ export function reify_message_id(opts) {
}
}
function register_click_handlers({on_click_scroll_to_selected}) {
export function register_click_handlers({on_click_scroll_to_selected}) {
$("#compose_banners").on(
"click",
".narrow_to_recipient .above_compose_banner_action_link",
(e) => {
const message_id = $(e.currentTarget).data("message-id");
narrow.by_topic(message_id, {trigger: "compose_notification"});
narrow.by_topic(message_id, {trigger: "notification"});
e.stopPropagation();
e.preventDefault();
},

View File

@ -7,7 +7,7 @@ import * as blueslip from "./blueslip";
import * as channel from "./channel";
import * as confirm_dialog from "./confirm_dialog";
import {$t, $t_html} from "./i18n";
import * as notifications from "./notifications";
import * as message_notifications from "./message_notifications";
import {page_params} from "./page_params";
import * as settings_config from "./settings_config";
import * as settings_org from "./settings_org";
@ -280,7 +280,7 @@ export function set_up(settings_panel) {
// intentionally don't let organization administrators set
// organization-level defaults.
$container.find(".send_test_notification").on("click", () => {
notifications.send_test_notification(
message_notifications.send_test_notification(
$t({defaultMessage: "This is what a Zulip notification looks like."}),
);
});

View File

@ -22,8 +22,8 @@ const {run_test} = require("./lib/test");
// replace them with {}.
const huddle_data = mock_esm("../src/huddle_data");
const message_lists = mock_esm("../src/message_lists");
const message_notifications = mock_esm("../src/message_notifications");
const message_util = mock_esm("../src/message_util");
const notifications = mock_esm("../src/notifications");
const pm_list = mock_esm("../src/pm_list");
const recent_view_data = mock_esm("../src/recent_view_data");
const stream_list = mock_esm("../src/stream_list");
@ -99,9 +99,9 @@ run_test("insert_message", ({override}) => {
assert.equal(message_store.get(new_message.id), undefined);
helper.redirect(huddle_data, "process_loaded_messages");
helper.redirect(message_notifications, "received_messages");
helper.redirect(message_util, "add_new_messages_data");
helper.redirect(message_util, "add_new_messages");
helper.redirect(notifications, "received_messages");
helper.redirect(recent_view_data, "process_message");
helper.redirect(stream_list, "update_streams_sidebar");
helper.redirect(unread_ops, "process_visible");
@ -122,7 +122,7 @@ run_test("insert_message", ({override}) => {
[message_util, "add_new_messages"],
[unread_ui, "update_unread_counts"],
[unread_ops, "process_visible"],
[notifications, "received_messages"],
[message_notifications, "received_messages"],
[stream_list, "update_streams_sidebar"],
[recent_view_data, "process_message"],
]);

View File

@ -9,7 +9,7 @@ const {page_params} = require("./lib/zpage_params");
const message_edit = mock_esm("../src/message_edit");
const message_lists = mock_esm("../src/message_lists");
const notifications = mock_esm("../src/notifications");
const message_notifications = mock_esm("../src/message_notifications");
const pm_list = mock_esm("../src/pm_list");
const stream_list = mock_esm("../src/stream_list");
const unread_ui = mock_esm("../src/unread_ui");
@ -99,7 +99,7 @@ run_test("update_messages", () => {
const side_effects = [
[message_edit, "end_message_edit"],
[notifications, "received_messages"],
[message_notifications, "received_messages"],
[unread_ui, "update_unread_counts"],
[stream_list, "update_streams_sidebar"],
[pm_list, "update_private_messages"],

View File

@ -13,6 +13,7 @@ const user_topics = zrequire("user_topics");
const stream_data = zrequire("stream_data");
const notifications = zrequire("notifications");
const message_notifications = zrequire("message_notifications");
// Not muted streams
const general = {
@ -82,10 +83,10 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "whatever",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
// Not notifiable because it was sent by the current user
assert.equal(notifications.message_is_notifiable(message), false);
assert.equal(message_notifications.message_is_notifiable(message), false);
// Case 2: If the user has already been sent a notification about this message,
// DO NOT notify the user
@ -103,9 +104,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "whatever",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), false);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), false);
// Case 3: If a message mentions the user directly,
// DO notify the user
@ -121,9 +122,9 @@ test("message_is_notifiable", () => {
stream_id: muted.stream_id,
topic: "topic_three",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), true);
// Case 4: If the message has been sent to a followed topic,
// DO visually and audibly notify the user if 'enable_followed_topic_desktop_notifications'
@ -140,17 +141,17 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "followed topic",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), true);
// But not if 'enable_followed_topic_desktop_notifications'
// and 'enable_followed_topic_audible_notifications' are disabled.
user_settings.enable_followed_topic_desktop_notifications = false;
user_settings.enable_followed_topic_audible_notifications = false;
assert.equal(notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state
user_settings.enable_followed_topic_desktop_notifications = true;
@ -168,9 +169,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "vanilla",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), true);
// Case 6:
// Wildcard mention should trigger notification in unmuted topic
@ -186,21 +187,21 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "vanilla",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), true);
// But not if it's disabled
user_settings.wildcard_mentions_notify = false;
assert.equal(notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(message_notifications.message_is_notifiable(message), true);
// And the stream-level setting overrides the global setting
general.wildcard_mentions_notify = true;
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state
user_settings.wildcard_mentions_notify = true;
@ -220,9 +221,9 @@ test("message_is_notifiable", () => {
stream_id: muted.stream_id,
topic: "whatever",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), false);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), false);
// Case 8: If a message is in a muted stream
// and does mention the user DIRECTLY,
@ -238,9 +239,9 @@ test("message_is_notifiable", () => {
stream_id: muted.stream_id,
topic: "whatever",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), true);
// Case 9: If a message is in a muted topic
// and does not mention the user DIRECTLY (i.e. wildcard mention),
@ -256,9 +257,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "muted topic",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), false);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), false);
// Case 10:
// Wildcard mentions in a followed topic with 'wildcard_mentions_notify',
@ -280,15 +281,15 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "followed topic",
};
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(message_notifications.message_is_notifiable(message), true);
// But not if 'enable_followed_topic_wildcard_mentions_notify' is disabled
user_settings.enable_followed_topic_wildcard_mentions_notify = false;
assert.equal(notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state
user_settings.wildcard_mentions_notify = true;
@ -310,9 +311,9 @@ test("message_is_notifiable", () => {
topic: "whatever",
};
user_settings.notification_sound = "none";
assert.equal(notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state
user_settings.notification_sound = "ding";
@ -332,9 +333,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id,
topic: "whatever",
};
assert.equal(notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true);
assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(message_notifications.message_is_notifiable(message), true);
});
test("basic_notifications", () => {
@ -393,7 +394,7 @@ test("basic_notifications", () => {
};
// Send notification.
notifications.process_notification({message: message_1, desktop_notify: true});
message_notifications.process_notification({message: message_1, desktop_notify: true});
n = notifications.get_notifications();
assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
assert.equal(n.size, 1);
@ -408,7 +409,7 @@ test("basic_notifications", () => {
// Send notification.
message_1.id = 1001;
notifications.process_notification({message: message_1, desktop_notify: true});
message_notifications.process_notification({message: message_1, desktop_notify: true});
n = notifications.get_notifications();
assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
assert.equal(n.size, 1);
@ -416,14 +417,14 @@ test("basic_notifications", () => {
// Process same message again. Notification count shouldn't increase.
message_1.id = 1002;
notifications.process_notification({message: message_1, desktop_notify: true});
message_notifications.process_notification({message: message_1, desktop_notify: true});
n = notifications.get_notifications();
assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
assert.equal(n.size, 1);
assert.equal(last_shown_message_id, message_1.id);
// Send another message. Notification count should increase.
notifications.process_notification({message: message_2, desktop_notify: true});
message_notifications.process_notification({message: message_2, desktop_notify: true});
n = notifications.get_notifications();
assert.equal(n.has("Gus Fring to general > lunch"), true);
assert.equal(n.has("Jesse Pinkman to general > whatever"), true);