From db20fd12e073856e959ff7532f577e42bbb4d8b5 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 6 Oct 2023 13:29:25 -0700 Subject: [PATCH] notifications: Split out message_notifications module. Signed-off-by: Anders Kaseorg --- tools/test-js-with-node | 1 + web/src/message_events.js | 5 +- web/src/message_notifications.js | 404 ++++++++++++++++++++++++++++++ web/src/notifications.js | 402 +---------------------------- web/src/settings_notifications.js | 4 +- web/tests/example5.test.js | 6 +- web/tests/message_events.test.js | 4 +- web/tests/notifications.test.js | 105 ++++---- 8 files changed, 472 insertions(+), 459 deletions(-) create mode 100644 web/src/message_notifications.js diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 0ed40956a1..623a51c662 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -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", diff --git a/web/src/message_events.js b/web/src/message_events.js index 0a0077d519..fb805b30b3 100644 --- a/web/src/message_events.js +++ b/web/src/message_events.js @@ -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); } diff --git a/web/src/message_notifications.js b/web/src/message_notifications.js new file mode 100644 index 0000000000..1f551daded --- /dev/null +++ b/web/src/message_notifications.js @@ -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 = $("
").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; + } + + // @- 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, + }, + ]); +} diff --git a/web/src/notifications.js b/web/src/notifications.js index 6c993b1c5a..cf5a7753e4 100644 --- a/web/src/notifications.js +++ b/web/src/notifications.js @@ -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 = $("
").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; - } - - // @- 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(); }, diff --git a/web/src/settings_notifications.js b/web/src/settings_notifications.js index 4713cecd9a..43a3c816d9 100644 --- a/web/src/settings_notifications.js +++ b/web/src/settings_notifications.js @@ -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."}), ); }); diff --git a/web/tests/example5.test.js b/web/tests/example5.test.js index 74702dfade..4ebbaf3ec9 100644 --- a/web/tests/example5.test.js +++ b/web/tests/example5.test.js @@ -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"], ]); diff --git a/web/tests/message_events.test.js b/web/tests/message_events.test.js index 073c982e50..8cde765ce2 100644 --- a/web/tests/message_events.test.js +++ b/web/tests/message_events.test.js @@ -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"], diff --git a/web/tests/notifications.test.js b/web/tests/notifications.test.js index ee8c569fbd..00e2b3b884 100644 --- a/web/tests/notifications.test.js +++ b/web/tests/notifications.test.js @@ -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);