From 52e59a9605616ece892eeeba9f12206431242a08 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 8 Nov 2024 02:33:34 -0800 Subject: [PATCH] web: Add setters for rewired variables. Signed-off-by: Anders Kaseorg --- web/shared/src/typing_status.ts | 34 ++++++++++++++---- web/src/activity.ts | 6 +++- web/src/activity_ui.ts | 12 +++++-- web/src/alert_words_ui.ts | 6 +++- web/src/all_messages_data.ts | 6 +++- web/src/bootstrap_typeahead.ts | 6 +++- web/src/browser_history.ts | 6 +++- web/src/buddy_data.ts | 23 +++++++++--- web/src/compose.js | 12 +++++-- web/src/compose_actions.ts | 48 ++++++++++++++++++++----- web/src/compose_banner.ts | 8 +++-- web/src/compose_closed_ui.ts | 6 +++- web/src/compose_pm_pill.ts | 12 +++++-- web/src/compose_recipient.ts | 30 +++++++++++++--- web/src/compose_reply.ts | 18 ++++++++-- web/src/compose_state.ts | 6 +++- web/src/compose_ui.ts | 57 ++++++++++++++++++++++++------ web/src/compose_validate.ts | 8 ++++- web/src/composebox_typeahead.ts | 12 +++++-- web/src/drafts.ts | 24 ++++++++++--- web/src/echo.ts | 20 ++++++++--- web/src/hotkey.js | 6 +++- web/src/message_events.js | 9 +++-- web/src/message_flags.ts | 6 +++- web/src/message_view.ts | 6 +++- web/src/narrow_state.ts | 36 +++++++++++++++---- web/src/peer_data.ts | 6 +++- web/src/people.ts | 26 +++++++++++--- web/src/pm_conversations.ts | 6 +++- web/src/pygments_data.ts | 6 ++-- web/src/reactions.ts | 56 +++++++++++++++++++++++------ web/src/recent_view_ui.ts | 6 +++- web/src/search.ts | 19 ++++++++-- web/src/settings_components.ts | 8 +++-- web/src/settings_org.ts | 14 ++++++-- web/src/stream_data.ts | 18 ++++++++-- web/src/stream_list.ts | 18 ++++++++-- web/src/stream_list_sort.ts | 6 +++- web/src/stream_topic_history.ts | 6 +++- web/src/sub_store.ts | 7 ++-- web/src/timerender.ts | 6 +++- web/src/typeahead_helper.ts | 62 +++++++++++++++++++++++++-------- web/src/ui_util.ts | 9 +++-- web/src/upload.ts | 14 ++++++-- web/src/user_topics.ts | 10 ++++-- web/src/util.ts | 6 +++- 46 files changed, 599 insertions(+), 133 deletions(-) diff --git a/web/shared/src/typing_status.ts b/web/shared/src/typing_status.ts index 463e5eb243..941c6d6f60 100644 --- a/web/shared/src/typing_status.ts +++ b/web/shared/src/typing_status.ts @@ -55,19 +55,27 @@ function same_recipient(a: Recipient | null, b: Recipient | null): boolean { /** Exported only for tests. */ export let state: TypingStatusState | null = null; +export function rewire_state(value: typeof state): void { + state = value; +} + /** Exported only for tests. */ -export function stop_last_notification(worker: TypingStatusWorker): void { +export let stop_last_notification = (worker: TypingStatusWorker): void => { assert(state !== null, "State object should not be null here."); clearTimeout(state.idle_timer); worker.notify_server_stop(state.current_recipient); state = null; +}; + +export function rewire_stop_last_notification(value: typeof stop_last_notification): void { + stop_last_notification = value; } /** Exported only for tests. */ -export function start_or_extend_idle_timer( +export let start_or_extend_idle_timer = ( worker: TypingStatusWorker, typing_stopped_wait_period: number, -): ReturnType { +): ReturnType => { function on_idle_timeout(): void { // We don't do any real error checking here, because // if we've been idle, we need to tell folks, and if @@ -80,6 +88,10 @@ export function start_or_extend_idle_timer( clearTimeout(state.idle_timer); } return setTimeout(on_idle_timeout, typing_stopped_wait_period); +}; + +export function rewire_start_or_extend_idle_timer(value: typeof start_or_extend_idle_timer): void { + start_or_extend_idle_timer = value; } function set_next_start_time(current_time: number, typing_started_wait_period: number): void { @@ -88,27 +100,35 @@ function set_next_start_time(current_time: number, typing_started_wait_period: n } // Exported for tests -export function actually_ping_server( +export let actually_ping_server = ( worker: TypingStatusWorker, recipient: Recipient, current_time: number, typing_started_wait_period: number, -): void { +): void => { worker.notify_server_start(recipient); set_next_start_time(current_time, typing_started_wait_period); +}; + +export function rewire_actually_ping_server(value: typeof actually_ping_server): void { + actually_ping_server = value; } /** Exported only for tests. */ -export function maybe_ping_server( +export let maybe_ping_server = ( worker: TypingStatusWorker, recipient: Recipient, typing_started_wait_period: number, -): void { +): void => { assert(state !== null, "State object should not be null here."); const current_time = worker.get_current_time(); if (current_time > state.next_send_start_time) { actually_ping_server(worker, recipient, current_time, typing_started_wait_period); } +}; + +export function rewire_maybe_ping_server(value: typeof maybe_ping_server): void { + maybe_ping_server = value; } /** diff --git a/web/src/activity.ts b/web/src/activity.ts index 7a2179303c..d2f119473b 100644 --- a/web/src/activity.ts +++ b/web/src/activity.ts @@ -114,7 +114,7 @@ export function compute_active_status(): ActivityState { return ActivityState.IDLE; } -export function send_presence_to_server(redraw?: () => void): void { +export let send_presence_to_server = (redraw?: () => void): void => { // Zulip has 2 data feeds coming from the server to the client: // The server_events data, and this presence feed. Data from // server_events is nicely serialized, but if we've been offline @@ -180,6 +180,10 @@ export function send_presence_to_server(redraw?: () => void): void { } }, }); +}; + +export function rewire_send_presence_to_server(value: typeof send_presence_to_server): void { + send_presence_to_server = value; } export function mark_client_active(): void { diff --git a/web/src/activity_ui.ts b/web/src/activity_ui.ts index 7148b8077f..87aa6ce2ac 100644 --- a/web/src/activity_ui.ts +++ b/web/src/activity_ui.ts @@ -59,7 +59,7 @@ export function clear_for_testing(): void { user_filter = undefined; } -export function update_presence_indicators(): void { +export let update_presence_indicators = (): void => { $("[data-presence-indicator-user-id]").each(function () { const user_id = Number.parseInt($(this).attr("data-presence-indicator-user-id") ?? "", 10); assert(!Number.isNaN(user_id)); @@ -68,6 +68,10 @@ export function update_presence_indicators(): void { .removeClass("user_circle_empty user_circle_green user_circle_idle") .addClass(user_circle_class); }); +}; + +export function rewire_update_presence_indicators(value: typeof update_presence_indicators): void { + update_presence_indicators = value; } export function redraw_user(user_id: number): void { @@ -115,7 +119,7 @@ export function render_empty_user_list_message_if_needed($container: JQuery): vo $container.append($(empty_list_widget_html)); } -export function build_user_sidebar(): number[] | undefined { +export let build_user_sidebar = (): number[] | undefined => { if (realm.realm_presence_disabled) { return undefined; } @@ -131,6 +135,10 @@ export function build_user_sidebar(): number[] | undefined { render_empty_user_list_message_if_needed(buddy_list.$other_users_list); return all_user_ids; // for testing +}; + +export function rewire_build_user_sidebar(value: typeof build_user_sidebar): void { + build_user_sidebar = value; } function do_update_users_for_search(): void { diff --git a/web/src/alert_words_ui.ts b/web/src/alert_words_ui.ts index 0398a68bf1..70b268aebc 100644 --- a/web/src/alert_words_ui.ts +++ b/web/src/alert_words_ui.ts @@ -12,7 +12,7 @@ import * as ui_report from "./ui_report"; export let loaded = false; -export function rerender_alert_words_ui(): void { +export let rerender_alert_words_ui = (): void => { if (!loaded) { return; } @@ -33,6 +33,10 @@ export function rerender_alert_words_ui(): void { ...ListWidget.generic_sort_functions("alphabetic", ["word"]), }, }); +}; + +export function rewire_rerender_alert_words_ui(value: typeof rerender_alert_words_ui): void { + rerender_alert_words_ui = value; } function update_alert_word_status(status_text: string, is_error: boolean): void { diff --git a/web/src/all_messages_data.ts b/web/src/all_messages_data.ts index e711bea910..d63641c466 100644 --- a/web/src/all_messages_data.ts +++ b/web/src/all_messages_data.ts @@ -1,7 +1,11 @@ import {Filter} from "./filter"; import {MessageListData} from "./message_list_data"; -export const all_messages_data = new MessageListData({ +export let all_messages_data = new MessageListData({ excludes_muted_topics: false, filter: new Filter([]), }); + +export function rewire_all_messages_data(value: typeof all_messages_data): void { + all_messages_data = value; +} diff --git a/web/src/bootstrap_typeahead.ts b/web/src/bootstrap_typeahead.ts index f4525adc75..e99ae6b8ba 100644 --- a/web/src/bootstrap_typeahead.ts +++ b/web/src/bootstrap_typeahead.ts @@ -186,7 +186,11 @@ export function defaultSorter(items: string[], query: string): string[] { return [...beginswith, ...caseSensitive, ...caseInsensitive]; } -export const MAX_ITEMS = 50; +export let MAX_ITEMS = 50; + +export function rewire_MAX_ITEMS(value: typeof MAX_ITEMS): void { + MAX_ITEMS = value; +} /* TYPEAHEAD PUBLIC CLASS DEFINITION * ================================= */ diff --git a/web/src/browser_history.ts b/web/src/browser_history.ts index 506b33014b..6ae141957e 100644 --- a/web/src/browser_history.ts +++ b/web/src/browser_history.ts @@ -58,7 +58,7 @@ export function save_old_hash(): boolean { return was_internal_change; } -export function update(new_hash: string): void { +export let update = (new_hash: string): void => { const old_hash = window.location.hash; if (!new_hash.startsWith("#")) { @@ -78,6 +78,10 @@ export function update(new_hash: string): void { state.old_hash = old_hash; state.is_internal_change = true; window.location.hash = new_hash; +}; + +export function rewire_update(value: typeof update): void { + update = value; } export function exit_overlay(): void { diff --git a/web/src/buddy_data.ts b/web/src/buddy_data.ts index 02b2475096..637f293651 100644 --- a/web/src/buddy_data.ts +++ b/web/src/buddy_data.ts @@ -27,8 +27,19 @@ import * as util from "./util"; */ -export const max_size_before_shrinking = 600; -export const max_channel_size_to_show_all_subscribers = 75; +export let max_size_before_shrinking = 600; + +export function rewire_max_size_before_shrinking(value: typeof max_size_before_shrinking): void { + max_size_before_shrinking = value; +} + +export let max_channel_size_to_show_all_subscribers = 75; + +export function rewire_max_channel_size_to_show_all_subscribers( + value: typeof max_channel_size_to_show_all_subscribers, +): void { + max_channel_size_to_show_all_subscribers = value; +} let is_searching_users = false; @@ -71,11 +82,11 @@ export function level(user_id: number): number { } } -export function user_matches_narrow( +export let user_matches_narrow = ( user_id: number, pm_ids: Set, stream_id?: number | null, -): boolean { +): boolean => { if (stream_id) { return stream_data.is_user_subscribed(stream_id, user_id); } @@ -83,6 +94,10 @@ export function user_matches_narrow( return pm_ids.has(user_id) || people.is_my_user_id(user_id); } return false; +}; + +export function rewire_user_matches_narrow(value: typeof user_matches_narrow): void { + user_matches_narrow = value; } export function compare_function( diff --git a/web/src/compose.js b/web/src/compose.js index 773d0c9907..d78e143a31 100644 --- a/web/src/compose.js +++ b/web/src/compose.js @@ -170,7 +170,7 @@ export function send_message_success(request, data) { } } -export function send_message(request = create_message_object()) { +export let send_message = (request = create_message_object()) => { compose_state.set_recipient_edited_manually(false); compose_state.set_is_content_unedited_restored_draft(false); if (request.type === "private") { @@ -269,6 +269,10 @@ export function send_message(request = create_message_object()) { // taking a longtime to send. setTimeout(() => echo.display_slow_send_loading_spinner(message), 5000); } +}; + +export function rewire_send_message(value) { + send_message = value; } export function enter_with_preview_open(ctrl_pressed = false) { @@ -287,7 +291,7 @@ export function enter_with_preview_open(ctrl_pressed = false) { // Common entrypoint for asking the server to send the message // currently drafted in the compose box, including for scheduled // messages. -export function finish(scheduling_message = false) { +export let finish = (scheduling_message = false) => { if (compose_ui.compose_spinner_visible) { // Avoid sending a message twice in parallel in races where // the user clicks the `Send` button very quickly twice or @@ -326,6 +330,10 @@ export function finish(scheduling_message = false) { } do_post_send_tasks(); return true; +}; + +export function rewire_finish(value) { + finish = value; } export function do_post_send_tasks() { diff --git a/web/src/compose_actions.ts b/web/src/compose_actions.ts index 611ac475b5..9738ccf9b1 100644 --- a/web/src/compose_actions.ts +++ b/web/src/compose_actions.ts @@ -70,8 +70,12 @@ function call_hooks(hooks: ComposeHook[]): void { } } -export function blur_compose_inputs(): void { +export let blur_compose_inputs = (): void => { $(".message_comp").find("input, textarea, button, #private_message_recipient").trigger("blur"); +}; + +export function rewire_blur_compose_inputs(value: typeof blur_compose_inputs): void { + blur_compose_inputs = value; } function hide_box(): void { @@ -109,8 +113,12 @@ function show_compose_box(opts: ComposeActionsOpts): void { compose_ui.set_focus(opts_by_message_type); } -export function clear_textarea(): void { +export let clear_textarea = (): void => { $("#compose").find("input[type=text], textarea").val(""); +}; + +export function rewire_clear_textarea(value: typeof clear_textarea): void { + clear_textarea = value; } function clear_box(): void { @@ -135,7 +143,7 @@ function clear_box(): void { } let autosize_callback_opts: ComposeActionsStartOpts; -export function autosize_message_content(opts: ComposeActionsStartOpts): void { +export let autosize_message_content = (opts: ComposeActionsStartOpts): void => { if (!compose_ui.is_expanded()) { autosize_callback_opts = opts; let has_resized_once = false; @@ -157,15 +165,23 @@ export function autosize_message_content(opts: ComposeActionsStartOpts): void { }); autosize($("textarea#compose-textarea")); } +}; + +export function rewire_autosize_message_content(value: typeof autosize_message_content): void { + autosize_message_content = value; } -export function expand_compose_box(): void { +export let expand_compose_box = (): void => { $("#compose_close").attr("data-tooltip-template-id", "compose_close_tooltip_template"); $("#compose_controls").hide(); $(".message_comp").show(); +}; + +export function rewire_expand_compose_box(value: typeof expand_compose_box): void { + expand_compose_box = value; } -export function complete_starting_tasks(opts: ComposeActionsOpts): void { +export let complete_starting_tasks = (opts: ComposeActionsOpts): void => { // This is sort of a kitchen sink function, and it's called only // by compose.start() for now. Having this as a separate function // makes testing a bit easier. @@ -182,6 +198,10 @@ export function complete_starting_tasks(opts: ComposeActionsOpts): void { if (!narrow_state.narrowed_by_reply()) { compose_notifications.maybe_show_one_time_interleaved_view_messages_fading_banner(); } +}; + +export function rewire_complete_starting_tasks(value: typeof complete_starting_tasks): void { + complete_starting_tasks = value; } export function maybe_scroll_up_selected_message(opts: ComposeActionsStartOpts): void { @@ -246,7 +266,7 @@ function same_recipient_as_before(opts: ComposeActionsOpts): boolean { ); } -export function start(raw_opts: ComposeActionsStartOpts): void { +export let start = (raw_opts: ComposeActionsStartOpts): void => { if (page_params.is_spectator) { spectators.login_to_access(); return; @@ -390,9 +410,13 @@ export function start(raw_opts: ComposeActionsStartOpts): void { resize.reset_compose_message_max_height(); complete_starting_tasks(opts); +}; + +export function rewire_start(value: typeof start): void { + start = value; } -export function cancel(): void { +export let cancel = (): void => { // As user closes the compose box, restore the compose box max height if (compose_ui.is_expanded()) { compose_ui.make_compose_box_original_size(); @@ -420,6 +444,10 @@ export function cancel(): void { compose_state.set_message_type(undefined); compose_pm_pill.clear(); $(document).trigger("compose_canceled.zulip"); +}; + +export function rewire_cancel(value: typeof cancel): void { + cancel = value; } export function on_show_navigation_view(): void { @@ -440,7 +468,7 @@ export function on_show_navigation_view(): void { cancel(); } -export function on_topic_narrow(): void { +export let on_topic_narrow = (): void => { if (!compose_state.composing()) { // If our compose box is closed, then just // leave it closed, assuming that the user is @@ -491,6 +519,10 @@ export function on_topic_narrow(): void { compose_fade.update_message_list(); drafts.update_compose_draft_count(); $("textarea#compose-textarea").trigger("focus"); +}; + +export function rewire_on_topic_narrow(value: typeof on_topic_narrow): void { + on_topic_narrow = value; } // TODO/typescript: Fill this in when converting narrow.js to typescripot. diff --git a/web/src/compose_banner.ts b/web/src/compose_banner.ts index c39293e9c2..fba50340aa 100644 --- a/web/src/compose_banner.ts +++ b/web/src/compose_banner.ts @@ -91,10 +91,10 @@ export function update_or_append_banner( } } -export function clear_message_sent_banners( +export let clear_message_sent_banners = ( include_unmute_banner = true, skip_automatic_new_visibility_policy_banner = false, -): void { +): void => { for (const classname of Object.values(MESSAGE_SENT_CLASSNAMES)) { if ( skip_automatic_new_visibility_policy_banner && @@ -115,6 +115,10 @@ export function clear_message_sent_banners( clear_unmute_topic_notifications(); } scroll_to_message_banner_message_id = null; +}; + +export function rewire_clear_message_sent_banners(value: typeof clear_message_sent_banners): void { + clear_message_sent_banners = value; } // TODO: Replace with compose_ui.hide_compose_spinner() when it is converted to ts. diff --git a/web/src/compose_closed_ui.ts b/web/src/compose_closed_ui.ts index 9949d59666..891755a847 100644 --- a/web/src/compose_closed_ui.ts +++ b/web/src/compose_closed_ui.ts @@ -61,7 +61,7 @@ export function get_recipient_label(message?: ComposeClosedMessage): string { } // Exported for tests -export function update_reply_button_state(disable = false): void { +export let update_reply_button_state = (disable = false): void => { $(".compose_reply_button").attr("disabled", disable ? "disabled" : null); if (disable) { $("#compose_buttons .compose-reply-button-wrapper").attr( @@ -81,6 +81,10 @@ export function update_reply_button_state(disable = false): void { "selected_conversation", ); } +}; + +export function rewire_update_reply_button_state(value: typeof update_reply_button_state): void { + update_reply_button_state = value; } function update_buttons(disable_reply?: boolean): void { diff --git a/web/src/compose_pm_pill.ts b/web/src/compose_pm_pill.ts index 8868014f71..8977c607e7 100644 --- a/web/src/compose_pm_pill.ts +++ b/web/src/compose_pm_pill.ts @@ -63,10 +63,14 @@ export function set_from_typeahead(person: User): void { }); } -export function set_from_emails(value: string): void { +export let set_from_emails = (value: string): void => { // value is something like "alice@example.com,bob@example.com" clear(); widget.appendValue(value); +}; + +export function rewire_set_from_emails(value: typeof set_from_emails): void { + set_from_emails = value; } export function get_user_ids(): number[] { @@ -84,11 +88,15 @@ export function get_user_ids_string(): string { return user_ids_string; } -export function get_emails(): string { +export let get_emails = (): string => { // return something like "alice@example.com,bob@example.com" const user_ids = get_user_ids(); const emails = user_ids.map((id) => people.get_by_user_id(id).email).join(","); return emails; +}; + +export function rewire_get_emails(value: typeof get_emails): void { + get_emails = value; } export function filter_taken_users(persons: User[]): User[] { diff --git a/web/src/compose_recipient.ts b/web/src/compose_recipient.ts index 7f5b61e2e1..8a47c5cdd8 100644 --- a/web/src/compose_recipient.ts +++ b/web/src/compose_recipient.ts @@ -62,7 +62,7 @@ function composing_to_current_private_message_narrow(): boolean { ); } -export function update_narrow_to_recipient_visibility(): void { +export let update_narrow_to_recipient_visibility = (): void => { const message_type = compose_state.get_message_type(); if (message_type === "stream") { const stream_exists = Boolean(compose_state.stream_id()); @@ -87,6 +87,12 @@ export function update_narrow_to_recipient_visibility(): void { } } $(".conversation-arrow").toggleClass("narrow_to_compose_recipients", false); +}; + +export function rewire_update_narrow_to_recipient_visibility( + value: typeof update_narrow_to_recipient_visibility, +): void { + update_narrow_to_recipient_visibility = value; } function update_fade(): void { @@ -131,7 +137,7 @@ export function get_posting_policy_error_message(): string { return ""; } -export function check_posting_policy_for_compose_box(): void { +export let check_posting_policy_for_compose_box = (): void => { const banner_text = get_posting_policy_error_message(); if (banner_text === "") { compose_validate.set_recipient_disallowed(false); @@ -147,6 +153,12 @@ export function check_posting_policy_for_compose_box(): void { } else { compose_banner.show_error_message(banner_text, banner_classname, $("#compose_banners")); } +}; + +export function rewire_check_posting_policy_for_compose_box( + value: typeof check_posting_policy_for_compose_box, +): void { + check_posting_policy_for_compose_box = value; } function switch_message_type(message_type: MessageType): void { @@ -207,7 +219,7 @@ export function update_compose_for_message_type(opts: ComposeTriggeredOptions): compose_banner.clear_uploads(); } -export function on_compose_select_recipient_update(): void { +export let on_compose_select_recipient_update = (): void => { const prev_message_type = compose_state.get_message_type(); let curr_message_type: MessageType = "stream"; @@ -226,6 +238,12 @@ export function on_compose_select_recipient_update(): void { } update_on_recipient_change(); +}; + +export function rewire_on_compose_select_recipient_update( + value: typeof on_compose_select_recipient_update, +): void { + on_compose_select_recipient_update = value; } export function possibly_update_stream_name_in_compose(stream_id: number): void { @@ -327,7 +345,7 @@ export function initialize(): void { }); } -export function update_placeholder_text(): void { +export let update_placeholder_text = (): void => { const $textarea: JQuery = $("textarea#compose-textarea"); // Change compose placeholder text only if compose box is open. if (!$textarea.is(":visible")) { @@ -352,4 +370,8 @@ export function update_placeholder_text(): void { $textarea.attr("placeholder", placeholder); compose_ui.autosize_textarea($textarea); +}; + +export function rewire_update_placeholder_text(value: typeof update_placeholder_text): void { + update_placeholder_text = value; } diff --git a/web/src/compose_reply.ts b/web/src/compose_reply.ts index 3b3b2cad6c..aaa9a04f33 100644 --- a/web/src/compose_reply.ts +++ b/web/src/compose_reply.ts @@ -22,12 +22,12 @@ import * as recent_view_util from "./recent_view_util"; import * as stream_data from "./stream_data"; import * as unread_ops from "./unread_ops"; -export function respond_to_message(opts: { +export let respond_to_message = (opts: { keep_composebox_empty?: boolean; message_id?: number; reply_type?: "personal"; trigger?: string; -}): void { +}): void => { let message; let msg_type: "private" | "stream"; if (recent_view_util.is_visible()) { @@ -147,6 +147,10 @@ export function respond_to_message(opts: { is_reply: true, keep_composebox_empty: opts.keep_composebox_empty, }); +}; + +export function rewire_respond_to_message(value: typeof respond_to_message): void { + respond_to_message = value; } export function reply_with_mention(opts: { @@ -166,7 +170,9 @@ export function reply_with_mention(opts: { compose_ui.insert_syntax_and_focus(mention); } -export function selection_within_message_id(selection = window.getSelection()): number | undefined { +export let selection_within_message_id = ( + selection = window.getSelection(), +): number | undefined => { // Returns the message_id if the selection is entirely within a message, // otherwise returns undefined. assert(selection !== null); @@ -178,6 +184,12 @@ export function selection_within_message_id(selection = window.getSelection()): return start_id; } return undefined; +}; + +export function rewire_selection_within_message_id( + value: typeof selection_within_message_id, +): void { + selection_within_message_id = value; } function get_quote_target(opts: {message_id?: number; quote_content?: string}): { diff --git a/web/src/compose_state.ts b/web/src/compose_state.ts index c7ff192663..f0839e859b 100644 --- a/web/src/compose_state.ts +++ b/web/src/compose_state.ts @@ -106,12 +106,16 @@ export function stream_id(): number | undefined { return undefined; } -export function stream_name(): string { +export let stream_name = (): string => { const stream_id = selected_recipient_id; if (typeof stream_id === "number") { return sub_store.maybe_get_stream_name(stream_id) ?? ""; } return ""; +}; + +export function rewire_stream_name(value: typeof stream_name): void { + stream_name = value; } export function set_stream_id(stream_id: number | ""): void { diff --git a/web/src/compose_ui.ts b/web/src/compose_ui.ts index 9b90051424..0a59705ae4 100644 --- a/web/src/compose_ui.ts +++ b/web/src/compose_ui.ts @@ -70,6 +70,11 @@ const message_render_response_schema = z.object({ }); export let compose_spinner_visible = false; + +export function rewire_compose_spinner_visible(value: typeof compose_spinner_visible): void { + compose_spinner_visible = value; +} + export let shift_pressed = false; // true or false export let code_formatting_button_triggered = false; // true or false export let compose_textarea_typeahead: Typeahead | undefined; @@ -102,20 +107,24 @@ export function is_full_size(): boolean { return full_size_status; } -export function autosize_textarea($textarea: JQuery): void { +export let autosize_textarea = ($textarea: JQuery): void => { // Since this supports both compose and file upload, one must pass // in the text area to autosize. if (!is_expanded()) { autosize.update($textarea); } +}; + +export function rewire_autosize_textarea(value: typeof autosize_textarea): void { + autosize_textarea = value; } -export function insert_and_scroll_into_view( +export let insert_and_scroll_into_view = ( content: string, $textarea: JQuery, replace_all = false, replace_all_without_undo_support = false, -): void { +): void => { if (replace_all_without_undo_support) { // setFieldText is very slow and noticeable when inserting 10k+ // characters of text like from a drafted response, @@ -132,6 +141,12 @@ export function insert_and_scroll_into_view( $textarea.trigger("blur"); $textarea.trigger("focus"); autosize_textarea($textarea); +}; + +export function rewire_insert_and_scroll_into_view( + value: typeof insert_and_scroll_into_view, +): void { + insert_and_scroll_into_view = value; } function get_focus_area(opts: ComposeTriggeredOptions): string { @@ -168,7 +183,7 @@ export function set_focus(opts: ComposeTriggeredOptions): void { } } -export function smart_insert_inline($textarea: JQuery, syntax: string): void { +export let smart_insert_inline = ($textarea: JQuery, syntax: string): void => { function is_space(c: string | undefined): boolean { return c === " " || c === "\t" || c === "\n"; } @@ -200,6 +215,10 @@ export function smart_insert_inline($textarea: JQuery, synt } insert_and_scroll_into_view(syntax, $textarea); +}; + +export function rewire_smart_insert_inline(value: typeof smart_insert_inline): void { + smart_insert_inline = value; } export function smart_insert_block( @@ -252,12 +271,12 @@ export function smart_insert_block( insert_and_scroll_into_view(syntax, $textarea); } -export function insert_syntax_and_focus( +export let insert_syntax_and_focus = ( syntax: string, $textarea = $("textarea#compose-textarea"), mode = "inline", padding_newlines?: number, -): void { +): void => { // Generic helper for inserting syntax into the main compose box // where the cursor was and focusing the area. Mostly a thin // wrapper around smart_insert_inline and smart_inline_block. @@ -277,13 +296,17 @@ export function insert_syntax_and_focus( } else if (mode === "block") { smart_insert_block($textarea, syntax, padding_newlines); } +}; + +export function rewire_insert_syntax_and_focus(value: typeof insert_syntax_and_focus): void { + insert_syntax_and_focus = value; } -export function replace_syntax( +export let replace_syntax = ( old_syntax: string, new_syntax: string, $textarea = $("textarea#compose-textarea"), -): boolean { +): boolean => { // The following couple lines are needed to later restore the initial // logical position of the cursor after the replacement const prev_caret = $textarea.caret(); @@ -322,6 +345,10 @@ export function replace_syntax( // Return if anything was actually replaced. return old_text !== new_text; +}; + +export function rewire_replace_syntax(value: typeof replace_syntax): void { + replace_syntax = value; } export function compute_placeholder_text(opts: ComposePlaceholderOptions): string { @@ -374,7 +401,7 @@ export function compute_placeholder_text(opts: ComposePlaceholderOptions): strin return DEFAULT_COMPOSE_PLACEHOLDER; } -export function set_compose_box_top(set_top: boolean): void { +export let set_compose_box_top = (set_top: boolean): void => { if (set_top) { // As `#compose` has `position: fixed` property, we cannot // make the compose-box to attain the correct height just by @@ -386,6 +413,10 @@ export function set_compose_box_top(set_top: boolean): void { } else { $("#compose").css("top", ""); } +}; + +export function rewire_set_compose_box_top(value: typeof set_compose_box_top): void { + set_compose_box_top = value; } export function make_compose_box_full_size(): void { @@ -504,11 +535,11 @@ export function position_inside_code_block(content: string, position: number): b return [...code_blocks].some((code_block) => code_block?.textContent?.includes(unique_insert)); } -export function format_text( +export let format_text = ( $textarea: JQuery, type: string, inserted_content = "", -): void { +): void => { const italic_syntax = "*"; const bold_syntax = "**"; const bold_and_italic_syntax = "***"; @@ -1156,6 +1187,10 @@ export function format_text( break; } } +}; + +export function rewire_format_text(value: typeof format_text): void { + format_text = value; } /* TODO: This functions don't belong in this module, as they have diff --git a/web/src/compose_validate.ts b/web/src/compose_validate.ts index 8a07bc762d..d7057cf141 100644 --- a/web/src/compose_validate.ts +++ b/web/src/compose_validate.ts @@ -476,7 +476,7 @@ function is_recipient_large_topic(): boolean { } // Exported for tests -export function wildcard_mention_policy_authorizes_user(): boolean { +export let wildcard_mention_policy_authorizes_user = (): boolean => { if ( realm.realm_wildcard_mention_policy === settings_config.wildcard_mention_policy_values.by_everyone.code @@ -518,6 +518,12 @@ export function wildcard_mention_policy_authorizes_user(): boolean { return days >= realm.realm_waiting_period_threshold && !current_user.is_guest; } return !current_user.is_guest; +}; + +export function rewire_wildcard_mention_policy_authorizes_user( + value: typeof wildcard_mention_policy_authorizes_user, +): void { + wildcard_mention_policy_authorizes_user = value; } export function stream_wildcard_mention_allowed(): boolean { diff --git a/web/src/composebox_typeahead.ts b/web/src/composebox_typeahead.ts index 4d99a1ca1f..703aff8acb 100644 --- a/web/src/composebox_typeahead.ts +++ b/web/src/composebox_typeahead.ts @@ -106,7 +106,11 @@ export type TypeaheadSuggestion = | SlashCommandSuggestion; // We export it to allow tests to mock it. -export const max_num_items = MAX_ITEMS; +export let max_num_items = MAX_ITEMS; + +export function rewire_max_num_items(value: typeof max_num_items): void { + max_num_items = value; +} export let emoji_collection: Emoji[] = []; @@ -389,9 +393,13 @@ function handle_keyup(e: JQuery.KeyUpEvent): void { } } -export function split_at_cursor(query: string, $input: JQuery): [string, string] { +export let split_at_cursor = (query: string, $input: JQuery): [string, string] => { const cursor = $input.caret(); return [query.slice(0, cursor), query.slice(cursor)]; +}; + +export function rewire_split_at_cursor(value: typeof split_at_cursor): void { + split_at_cursor = value; } export function tokenize_compose_str(s: string): string { diff --git a/web/src/drafts.ts b/web/src/drafts.ts index db5d87c25e..f6daa8e87b 100644 --- a/web/src/drafts.ts +++ b/web/src/drafts.ts @@ -22,9 +22,13 @@ import * as timerender from "./timerender"; import * as ui_util from "./ui_util"; import * as util from "./util"; -export function set_count(count: number): void { +export let set_count = (count: number): void => { const $drafts_li = $(".top_left_drafts"); ui_util.update_unread_count_in_dom($drafts_li, count); +}; + +export function rewire_set_count(value: typeof set_count): void { + set_count = value; } function getTimestamp(): number { @@ -219,7 +223,7 @@ export const draft_model = (function () { }; })(); -export function update_compose_draft_count(): void { +export let update_compose_draft_count = (): void => { const $count_container = $(".compose-drafts-count-container"); const $count_ele = $count_container.find(".compose-drafts-count"); if (!compose_state.has_full_recipient()) { @@ -235,11 +239,19 @@ export function update_compose_draft_count(): void { $count_ele.text(""); $count_container.hide(); } +}; + +export function rewire_update_compose_draft_count(value: typeof update_compose_draft_count): void { + update_compose_draft_count = value; } -export function sync_count(): void { +export let sync_count = (): void => { const drafts = draft_model.get(); set_count(Object.keys(drafts).length); +}; + +export function rewire_sync_count(value: typeof sync_count): void { + sync_count = value; } export function delete_all_drafts(): void { @@ -396,7 +408,7 @@ type UpdateDraftOptions = { is_sending_saving?: boolean; }; -export function update_draft(opts: UpdateDraftOptions = {}): string | undefined { +export let update_draft = (opts: UpdateDraftOptions = {}): string | undefined => { const draft_id = compose_draft_id; const old_draft = draft_id === undefined ? undefined : draft_model.getDraft(draft_id); @@ -439,6 +451,10 @@ export function update_draft(opts: UpdateDraftOptions = {}): string | undefined maybe_notify(no_notify); return new_draft_id; +}; + +export function rewire_update_draft(value: typeof update_draft): void { + update_draft = value; } export const DRAFT_LIFETIME = 30; diff --git a/web/src/echo.ts b/web/src/echo.ts index 695b1712dd..3726fd3429 100644 --- a/web/src/echo.ts +++ b/web/src/echo.ts @@ -288,14 +288,14 @@ export function is_slash_command(content: string): boolean { return !content.startsWith("/me") && content.startsWith("/"); } -export function try_deliver_locally( +export let try_deliver_locally = ( message_request: MessageRequest, insert_new_messages: ( messages: LocalMessage[], send_by_this_client: boolean, deliver_locally: boolean, ) => Message[], -): Message | undefined { +): Message | undefined => { // Checks if the message request can be locally echoed, and if so, // adds a local echoed copy of the message to appropriate message lists. // @@ -349,6 +349,10 @@ export function try_deliver_locally( const message = insert_local_message(message_request, local_id_float, insert_new_messages); return message; +}; + +export function rewire_try_deliver_locally(value: typeof try_deliver_locally): void { + try_deliver_locally = value; } export function edit_locally(message: Message, request: LocalEditRequest): Message { @@ -432,7 +436,7 @@ export function edit_locally(message: Message, request: LocalEditRequest): Messa return message; } -export function reify_message_id(local_id: string, server_id: number): void { +export let reify_message_id = (local_id: string, server_id: number): void => { const message = echo_state.get_message_waiting_for_id(local_id); echo_state.remove_message_from_waiting_for_id(local_id); @@ -463,6 +467,10 @@ export function reify_message_id(local_id: string, server_id: number): void { message_id: message.id, }); } +}; + +export function rewire_reify_message_id(value: typeof reify_message_id): void { + reify_message_id = value; } export function update_message_lists({old_id, new_id}: {old_id: number; new_id: number}): void { @@ -576,13 +584,17 @@ export function process_from_server(messages: ServerMessage[]): ServerMessage[] return non_echo_messages; } -export function message_send_error(message_id: number, error_response: string): void { +export let message_send_error = (message_id: number, error_response: string): void => { // Error sending message, show inline const message = message_store.get(message_id)!; message.failed_request = true; message.show_slow_send_spinner = false; show_message_failed(message_id, error_response); +}; + +export function rewire_message_send_error(value: typeof message_send_error): void { + message_send_error = value; } function abort_message(message: Message): void { diff --git a/web/src/hotkey.js b/web/src/hotkey.js index 735a9bbaf7..a7b751c2be 100644 --- a/web/src/hotkey.js +++ b/web/src/hotkey.js @@ -250,7 +250,7 @@ export function get_keypress_hotkey(e) { return keypress_mappings[e.which]; } -export function processing_text() { +export let processing_text = () => { const $focused_elt = $(":focus"); return ( $focused_elt.is("input") || @@ -260,6 +260,10 @@ export function processing_text() { $focused_elt.attr("id") === "compose-send-button" || $focused_elt.parents(".dropdown-list-container").length >= 1 ); +}; + +export function rewire_processing_text(value) { + processing_text = value; } export function in_content_editable_widget(e) { diff --git a/web/src/message_events.js b/web/src/message_events.js index 2c7b7e9e2d..c2c19ccbdb 100644 --- a/web/src/message_events.js +++ b/web/src/message_events.js @@ -103,11 +103,11 @@ export function update_current_view_for_topic_visibility() { return false; } -export function update_views_filtered_on_message_property( +export let update_views_filtered_on_message_property = ( message_ids, property_term_type, property_value, -) { +) => { // NOTE: Call this function after updating the message property locally. assert(!property_term_type.includes("not-")); @@ -212,6 +212,7 @@ export function update_views_filtered_on_message_property( // can be used to update other message lists and // cached message data structures as well. }, + // eslint-disable-next-line no-loop-func success(data) { // `messages_to_fetch` might already be cached locally when // we reach here but `message_helper.process_new_message` @@ -258,6 +259,10 @@ export function update_views_filtered_on_message_property( } } } +}; + +export function rewire_update_views_filtered_on_message_property(value) { + update_views_filtered_on_message_property = value; } export function insert_new_messages(messages, sent_by_this_client, deliver_locally) { diff --git a/web/src/message_flags.ts b/web/src/message_flags.ts index 0014a60c54..414463f249 100644 --- a/web/src/message_flags.ts +++ b/web/src/message_flags.ts @@ -16,7 +16,11 @@ export function send_flag_update_for_messages(msg_ids: number[], flag: string, o }, }); } -export const _unread_batch_size = 1000; +export let _unread_batch_size = 1000; + +export function rewire__unread_batch_size(value: typeof _unread_batch_size): void { + _unread_batch_size = value; +} export const send_read = (function () { let queue: Message[] = []; diff --git a/web/src/message_view.ts b/web/src/message_view.ts index 406eef2af9..5b1626f841 100644 --- a/web/src/message_view.ts +++ b/web/src/message_view.ts @@ -351,7 +351,7 @@ type ShowMessageViewOpts = { show_more_topics?: boolean; }; -export function show(raw_terms: NarrowTerm[], show_opts: ShowMessageViewOpts): void { +export let show = (raw_terms: NarrowTerm[], show_opts: ShowMessageViewOpts): void => { /* Main entry point for switching to a new view / message list. Supported parameters: @@ -777,6 +777,10 @@ export function show(raw_terms: NarrowTerm[], show_opts: ShowMessageViewOpts): v resize.resize_stream_filters_container(); }); }); +}; + +export function rewire_show(value: typeof show): void { + show = value; } function navigate_to_anchor_message(opts: { diff --git a/web/src/narrow_state.ts b/web/src/narrow_state.ts index 5158602731..04421dc4da 100644 --- a/web/src/narrow_state.ts +++ b/web/src/narrow_state.ts @@ -114,7 +114,7 @@ export function set_compose_defaults(): { return opts; } -export function stream_id(current_filter: Filter | undefined = filter()): number | undefined { +export let stream_id = (current_filter: Filter | undefined = filter()): number | undefined => { if (current_filter === undefined) { return undefined; } @@ -123,6 +123,10 @@ export function stream_id(current_filter: Filter | undefined = filter()): number return Number.parseInt(stream_operands[0], 10); } return undefined; +}; + +export function rewire_stream_id(value: typeof stream_id): void { + stream_id = value; } export function stream_name(current_filter: Filter | undefined = filter()): string | undefined { @@ -148,7 +152,7 @@ export function stream_sub( return stream_data.get_sub_by_id_string(stream_operands[0]); } -export function topic(current_filter: Filter | undefined = filter()): string | undefined { +export let topic = (current_filter: Filter | undefined = filter()): string | undefined => { if (current_filter === undefined) { return undefined; } @@ -157,6 +161,10 @@ export function topic(current_filter: Filter | undefined = filter()): string | u return operands[0]; } return undefined; +}; + +export function rewire_topic(value: typeof topic): void { + topic = value; } export function pm_ids_string(filter?: Filter): string | undefined { @@ -173,10 +181,14 @@ export function pm_ids_string(filter?: Filter): string | undefined { return user_ids_string; } -export function pm_ids_set(filter?: Filter): Set { +export let pm_ids_set = (filter?: Filter): Set => { const ids_string = pm_ids_string(filter); const pm_ids_list = ids_string ? people.user_ids_string_to_ids_array(ids_string) : []; return new Set(pm_ids_list); +}; + +export function rewire_pm_ids_set(value: typeof pm_ids_set): void { + pm_ids_set = value; } export function pm_emails_string( @@ -194,9 +206,9 @@ export function pm_emails_string( return operands[0]; } -export function get_first_unread_info( +export let get_first_unread_info = ( current_filter: Filter | undefined = filter(), -): {flavor: "cannot_compute" | "not_found"} | {flavor: "found"; msg_id: number} { +): {flavor: "cannot_compute" | "not_found"} | {flavor: "found"; msg_id: number} => { const cannot_compute_response: {flavor: "cannot_compute"} = {flavor: "cannot_compute"}; if (current_filter === undefined) { // we don't yet support the all-messages view @@ -231,11 +243,15 @@ export function get_first_unread_info( flavor: "found", msg_id, }; +}; + +export function rewire_get_first_unread_info(value: typeof get_first_unread_info): void { + get_first_unread_info = value; } -export function _possible_unread_message_ids( +export let _possible_unread_message_ids = ( current_filter: Filter | undefined = filter(), -): number[] | undefined { +): number[] | undefined => { // This function currently only returns valid results for // certain types of narrows, mostly left sidebar narrows. // For more complicated narrows we may return undefined. @@ -318,6 +334,12 @@ export function _possible_unread_message_ids( } return undefined; +}; + +export function rewire__possible_unread_message_ids( + value: typeof _possible_unread_message_ids, +): void { + _possible_unread_message_ids = value; } // Are we narrowed to direct messages: the direct message feed or a diff --git a/web/src/peer_data.ts b/web/src/peer_data.ts index 20b1fd8093..1e072e19f5 100644 --- a/web/src/peer_data.ts +++ b/web/src/peer_data.ts @@ -69,7 +69,7 @@ export function potential_subscribers(stream_id: number): User[] { return people.filter_all_users(is_potential_subscriber); } -export function get_subscriber_count(stream_id: number, include_bots = true): number { +export let get_subscriber_count = (stream_id: number, include_bots = true): number => { if (include_bots) { return get_user_set(stream_id).size; } @@ -81,6 +81,10 @@ export function get_subscriber_count(stream_id: number, include_bots = true): nu } } return count; +}; + +export function rewire_get_subscriber_count(value: typeof get_subscriber_count): void { + get_subscriber_count = value; } export function get_subscribers(stream_id: number): number[] { diff --git a/web/src/people.ts b/web/src/people.ts index f388c13852..c06615b5bb 100644 --- a/web/src/people.ts +++ b/web/src/people.ts @@ -89,10 +89,14 @@ export function get_users_from_ids(user_ids: number[]): User[] { } // Use this function only when you are sure that user_id is valid. -export function get_by_user_id(user_id: number): User { +export let get_by_user_id = (user_id: number): User => { const person = people_by_user_id_dict.get(user_id); assert(person, `Unknown user_id in get_by_user_id: ${user_id}`); return person; +}; + +export function rewire_get_by_user_id(value: typeof get_by_user_id): void { + get_by_user_id = value; } // This is type unsafe version of get_by_user_id for the callers that expects undefined values. @@ -123,7 +127,7 @@ export function validate_user_ids(user_ids: number[]): number[] { return good_ids; } -export function get_by_email(email: string): User | undefined { +export let get_by_email = (email: string): User | undefined => { const person = people_dict.get(email); if (!person) { @@ -137,6 +141,10 @@ export function get_by_email(email: string): User | undefined { } return person; +}; + +export function rewire_get_by_email(value: typeof get_by_email): void { + get_by_email = value; } export function get_bot_owner_user(user: User & {is_bot: true}): User | undefined { @@ -382,7 +390,7 @@ export function emails_strings_to_user_ids_string(emails_string: string): string return email_list_to_user_ids_string(emails); } -export function email_list_to_user_ids_string(emails: string[]): string | undefined { +export let email_list_to_user_ids_string = (emails: string[]): string | undefined => { let user_ids = util.try_parse_as_truthy( emails.map((email) => { const person = get_by_email(email); @@ -398,6 +406,12 @@ export function email_list_to_user_ids_string(emails: string[]): string | undefi user_ids = sort_numerically(user_ids); return user_ids.join(","); +}; + +export function rewire_email_list_to_user_ids_string( + value: typeof email_list_to_user_ids_string, +): void { + email_list_to_user_ids_string = value; } export function get_full_names_for_poll_option(user_ids: number[]): string { @@ -1056,7 +1070,7 @@ export function get_bot_ids(): number[] { return bot_ids; } -export function get_active_human_count(): number { +export let get_active_human_count = (): number => { let count = 0; for (const person of active_user_dict.values()) { if (!person.is_bot) { @@ -1064,6 +1078,10 @@ export function get_active_human_count(): number { } } return count; +}; + +export function rewire_get_active_human_count(value: typeof get_active_human_count): void { + get_active_human_count = value; } export function get_active_user_ids(): number[] { diff --git a/web/src/pm_conversations.ts b/web/src/pm_conversations.ts index 8340fc76a1..83617bb3e2 100644 --- a/web/src/pm_conversations.ts +++ b/web/src/pm_conversations.ts @@ -11,8 +11,12 @@ type PMConversation = { const partners = new Set(); -export function set_partner(user_id: number): void { +export let set_partner = (user_id: number): void => { partners.add(user_id); +}; + +export function rewire_set_partner(value: typeof set_partner): void { + set_partner = value; } export function is_partner(user_id: number): boolean { diff --git a/web/src/pygments_data.ts b/web/src/pygments_data.ts index 6a2a404324..c41b457702 100644 --- a/web/src/pygments_data.ts +++ b/web/src/pygments_data.ts @@ -1,6 +1,8 @@ import generated_pygments_data from "../generated/pygments_data.json"; type PygmentsLanguage = {priority: number; pretty_name: string}; -const langs: Record = generated_pygments_data.langs; +export let langs: Record = generated_pygments_data.langs; -export {langs}; +export function rewire_langs(value: typeof langs): void { + langs = value; +} diff --git a/web/src/reactions.ts b/web/src/reactions.ts index fa6df71a61..475b886051 100644 --- a/web/src/reactions.ts +++ b/web/src/reactions.ts @@ -249,10 +249,14 @@ export function get_reaction_sections(message_id: number): JQuery { return $rows.find(".message_reactions"); } -export function find_reaction(message_id: number, local_id: string): JQuery { +export let find_reaction = (message_id: number, local_id: string): JQuery => { const $reaction_section = get_reaction_sections(message_id); const $reaction = $reaction_section.find(`[data-reaction-id='${CSS.escape(local_id)}']`); return $reaction; +}; + +export function rewire_find_reaction(value: typeof find_reaction): void { + find_reaction = value; } export function get_add_reaction_button(message_id: number): JQuery { @@ -261,12 +265,16 @@ export function get_add_reaction_button(message_id: number): JQuery { return $add_button; } -export function set_reaction_vote_text($reaction: JQuery, vote_text: string): void { +export let set_reaction_vote_text = ($reaction: JQuery, vote_text: string): void => { const $count_element = $reaction.find(".message_reaction_count"); $count_element.text(vote_text); +}; + +export function rewire_set_reaction_vote_text(value: typeof set_reaction_vote_text): void { + set_reaction_vote_text = value; } -export function add_reaction(event: ReactionEvent): void { +export let add_reaction = (event: ReactionEvent): void => { const message_id = event.message_id; const message = message_store.get(message_id); @@ -311,13 +319,17 @@ export function add_reaction(event: ReactionEvent): void { message.clean_reactions.set(local_id, clean_reaction_object); insert_new_reaction(clean_reaction_object, message, user_id); } +}; + +export function rewire_add_reaction(value: typeof add_reaction): void { + add_reaction = value; } -export function update_existing_reaction( +export let update_existing_reaction = ( clean_reaction_object: MessageCleanReaction, message: Message, acting_user_id: number, -): void { +): void => { // Our caller ensures that this message already has a reaction // for this emoji and sets up our user_list. This function // simply updates the DOM. @@ -335,13 +347,17 @@ export function update_existing_reaction( } update_vote_text_on_message(message); +}; + +export function rewire_update_existing_reaction(value: typeof update_existing_reaction): void { + update_existing_reaction = value; } -export function insert_new_reaction( +export let insert_new_reaction = ( clean_reaction_object: MessageCleanReaction, message: Message, user_id: number, -): void { +): void => { // Our caller ensures we are the first user to react to this // message with this emoji. We then render the emoji/title/count // and insert it before the add button. @@ -389,9 +405,13 @@ export function insert_new_reaction( } update_vote_text_on_message(message); +}; + +export function rewire_insert_new_reaction(value: typeof insert_new_reaction): void { + insert_new_reaction = value; } -export function remove_reaction(event: ReactionEvent): void { +export let remove_reaction = (event: ReactionEvent): void => { const message_id = event.message_id; const user_id = event.user_id; const message = message_store.get(message_id); @@ -426,13 +446,17 @@ export function remove_reaction(event: ReactionEvent): void { update_user_fields(clean_reaction_object, should_display_reactors); remove_reaction_from_view(clean_reaction_object, message, user_id); +}; + +export function rewire_remove_reaction(value: typeof remove_reaction): void { + remove_reaction = value; } -export function remove_reaction_from_view( +export let remove_reaction_from_view = ( clean_reaction_object: MessageCleanReaction, message: Message, user_id: number, -): void { +): void => { const local_id = get_local_reaction_id(clean_reaction_object); const $reaction = find_reaction(message.id, local_id); const reaction_count = clean_reaction_object.user_ids.length; @@ -466,6 +490,10 @@ export function remove_reaction_from_view( } update_vote_text_on_message(message); +}; + +export function rewire_remove_reaction_from_view(value: typeof remove_reaction_from_view): void { + remove_reaction_from_view = value; } export function get_emojis_used_by_user_for_message_id(message_id: number): string[] { @@ -687,7 +715,7 @@ function comma_separated_usernames(user_list: number[]): string { return comma_separated_usernames; } -export function update_vote_text_on_message(message: Message): void { +export let update_vote_text_on_message = (message: Message): void => { // Because whether we display a count or the names of reacting // users depends on total reactions on the message, we need to // recalculate this whenever adjusting reaction rendering on a @@ -703,4 +731,10 @@ export function update_vote_text_on_message(message: Message): void { message_clean_reaction.vote_text = vote_text; set_reaction_vote_text(reaction_elem, vote_text); } +}; + +export function rewire_update_vote_text_on_message( + value: typeof update_vote_text_on_message, +): void { + update_vote_text_on_message = value; } diff --git a/web/src/recent_view_ui.ts b/web/src/recent_view_ui.ts index 2616cdae15..0fa6f5608a 100644 --- a/web/src/recent_view_ui.ts +++ b/web/src/recent_view_ui.ts @@ -930,7 +930,7 @@ export function bulk_inplace_rerender(row_keys: string[]): void { setTimeout(revive_current_focus, 0); } -export function inplace_rerender(topic_key: string, is_bulk_rerender?: boolean): boolean { +export let inplace_rerender = (topic_key: string, is_bulk_rerender?: boolean): boolean => { if (!recent_view_util.is_visible()) { return false; } @@ -990,6 +990,10 @@ export function inplace_rerender(topic_key: string, is_bulk_rerender?: boolean): setTimeout(revive_current_focus, 0); } return true; +}; + +export function rewire_inplace_rerender(value: typeof inplace_rerender): void { + inplace_rerender = value; } export function update_topic_visibility_policy(stream_id: number, topic: string): boolean { diff --git a/web/src/search.ts b/web/src/search.ts index 931e9c4f47..92dfb8b66a 100644 --- a/web/src/search.ts +++ b/web/src/search.ts @@ -17,6 +17,11 @@ import * as util from "./util"; // Exported for unit testing export let is_using_input_method = false; + +export function rewire_is_using_input_method(value: typeof is_using_input_method): void { + is_using_input_method = value; +} + export let search_pill_widget: SearchPillWidget | null = null; let search_input_has_changed = false; @@ -390,7 +395,7 @@ function reset_searchbox(clear = false): void { } // Exported for tests -export function exit_search(opts: {keep_search_narrow_open: boolean}): void { +export let exit_search = (opts: {keep_search_narrow_open: boolean}): void => { const filter = narrow_state.filter(); if (!filter || filter.is_common_narrow()) { // for common narrows, we change the UI (and don't redirect) @@ -404,13 +409,23 @@ export function exit_search(opts: {keep_search_narrow_open: boolean}): void { } $("#search_query").trigger("blur"); $(".app").trigger("focus"); +}; + +export function rewire_exit_search(value: typeof exit_search): void { + exit_search = value; } -export function open_search_bar_and_close_narrow_description(clear = false): void { +export let open_search_bar_and_close_narrow_description = (clear = false): void => { reset_searchbox(clear); $(".navbar-search").addClass("expanded"); $("#message_view_header").addClass("hidden"); popovers.hide_all(); +}; + +export function rewire_open_search_bar_and_close_narrow_description( + value: typeof open_search_bar_and_close_narrow_description, +): void { + open_search_bar_and_close_narrow_description = value; } export function close_search_bar_and_open_narrow_description(): void { diff --git a/web/src/settings_components.ts b/web/src/settings_components.ts index e44a7b354a..6bfe911c7e 100644 --- a/web/src/settings_components.ts +++ b/web/src/settings_components.ts @@ -631,10 +631,10 @@ export function get_input_type($input_elem: JQuery, input_type?: string): string return input_type; } -export function get_input_element_value( +export let get_input_element_value = ( input_elem: HTMLElement, input_type?: string, -): boolean | number | string | null | undefined | GroupSettingValue { +): boolean | number | string | null | undefined | GroupSettingValue => { const $input_elem = $(input_elem); input_type = get_input_type($input_elem, input_type); let input_value; @@ -691,6 +691,10 @@ export function get_input_element_value( default: return undefined; } +}; + +export function rewire_get_input_element_value(value: typeof get_input_element_value): void { + get_input_element_value = value; } export function set_input_element_value( diff --git a/web/src/settings_org.ts b/web/src/settings_org.ts index afdab19218..54bdbc0c6d 100644 --- a/web/src/settings_org.ts +++ b/web/src/settings_org.ts @@ -899,7 +899,7 @@ export function set_up_dropdown_widget_for_realm_group_settings(): void { } } -export function init_dropdown_widgets(): void { +export let init_dropdown_widgets = (): void => { const notification_stream_options = (): dropdown_widget.Option[] => { const streams = stream_settings_data.get_streams_for_settings_page(); const options: dropdown_widget.Option[] = streams.map((stream) => ({ @@ -956,6 +956,10 @@ export function init_dropdown_widgets(): void { ); set_up_dropdown_widget_for_realm_group_settings(); +}; + +export function rewire_init_dropdown_widgets(value: typeof init_dropdown_widgets): void { + init_dropdown_widgets = value; } export function register_save_discard_widget_handlers( @@ -1055,7 +1059,7 @@ export function register_save_discard_widget_handlers( } // Exported for tests -export function initialize_group_setting_widgets(): void { +export let initialize_group_setting_widgets = (): void => { const realm_group_permission_settings = Object.entries( realm.server_supported_permission_settings.realm, ); @@ -1080,6 +1084,12 @@ export function initialize_group_setting_widgets(): void { enable_or_disable_group_permission_settings(); check_disable_direct_message_initiator_group_widget(); +}; + +export function rewire_initialize_group_setting_widgets( + value: typeof initialize_group_setting_widgets, +): void { + initialize_group_setting_widgets = value; } export function build_page(): void { diff --git a/web/src/stream_data.ts b/web/src/stream_data.ts index 682ae78e0e..3539276f8b 100644 --- a/web/src/stream_data.ts +++ b/web/src/stream_data.ts @@ -217,7 +217,7 @@ export function get_stream_name_from_id(stream_id: number): string { return get_sub_by_id(stream_id)?.name ?? ""; } -export function get_sub_by_name(name: string): StreamSubscription | undefined { +export let get_sub_by_name = (name: string): StreamSubscription | undefined => { // Note: Only use this function for situations where // you are comfortable with a user dealing with an // old name of a stream (from prior to a rename). @@ -230,6 +230,10 @@ export function get_sub_by_name(name: string): StreamSubscription | undefined { } return sub_store.get(stream_id); +}; + +export function rewire_get_sub_by_name(value: typeof get_sub_by_name): void { + get_sub_by_name = value; } export function id_to_slug(stream_id: number): string { @@ -449,7 +453,7 @@ export function canonicalized_name(stream_name: string): string { return stream_name.toString().toLowerCase(); } -export function get_color(stream_id: number | undefined): string { +export let get_color = (stream_id: number | undefined): string => { if (stream_id === undefined) { return DEFAULT_COLOR; } @@ -458,6 +462,10 @@ export function get_color(stream_id: number | undefined): string { return DEFAULT_COLOR; } return sub.color; +}; + +export function rewire_get_color(value: typeof get_color): void { + get_color = value; } export function is_muted(stream_id: number): boolean { @@ -675,7 +683,7 @@ export function is_default_stream_id(stream_id: number): boolean { return default_stream_ids.has(stream_id); } -export function is_user_subscribed(stream_id: number, user_id: number): boolean { +export let is_user_subscribed = (stream_id: number, user_id: number): boolean => { const sub = sub_store.get(stream_id); if (sub === undefined || !can_view_subscribers(sub)) { // If we don't know about the stream, or we ourselves cannot access subscriber list, @@ -691,6 +699,10 @@ export function is_user_subscribed(stream_id: number, user_id: number): boolean } return peer_data.is_user_subscribed(stream_id, user_id); +}; + +export function rewire_is_user_subscribed(value: typeof is_user_subscribed): void { + is_user_subscribed = value; } export function create_streams(streams: Stream[]): void { diff --git a/web/src/stream_list.ts b/web/src/stream_list.ts index e1fd2ac8a5..7a0035632d 100644 --- a/web/src/stream_list.ts +++ b/web/src/stream_list.ts @@ -41,6 +41,10 @@ let zoomed_in = false; export let stream_cursor: ListCursor; +export function rewire_stream_cursor(value: typeof stream_cursor): void { + stream_cursor = value; +} + let has_scrolled = false; export function is_zoomed_in(): boolean { @@ -97,13 +101,13 @@ export function clear_topics(): void { zoomed_in = false; } -export function update_count_in_dom( +export let update_count_in_dom = ( $stream_li: JQuery, stream_counts: StreamCountInfo, stream_has_any_unread_mention_messages: boolean, stream_has_any_unmuted_unread_mention: boolean, stream_has_only_muted_unread_mention: boolean, -): void { +): void => { // The subscription_block properly excludes the topic list, // and it also has sensitive margins related to whether the // count is there or not. @@ -176,6 +180,10 @@ export function update_count_in_dom( $subscription_block.removeClass("has-only-muted-unreads"); $subscription_block.removeClass("stream-with-count"); } +}; + +export function rewire_update_count_in_dom(value: typeof update_count_in_dom): void { + update_count_in_dom = value; } class StreamSidebar { @@ -567,7 +575,7 @@ function set_stream_unread_count( ); } -export function update_streams_sidebar(force_rerender = false): void { +export let update_streams_sidebar = (force_rerender = false): void => { if (!force_rerender && is_zoomed_in()) { // We do our best to update topics that are displayed // in case user zoomed in. Streams list will be updated, @@ -591,6 +599,10 @@ export function update_streams_sidebar(force_rerender = false): void { } update_stream_sidebar_for_narrow(filter); +}; + +export function rewire_update_streams_sidebar(value: typeof update_streams_sidebar): void { + update_streams_sidebar = value; } export function update_dom_with_unread_counts(counts: FullUnreadCountsData): void { diff --git a/web/src/stream_list_sort.ts b/web/src/stream_list_sort.ts index 2b3bad124a..36f9ed9618 100644 --- a/web/src/stream_list_sort.ts +++ b/web/src/stream_list_sort.ts @@ -58,7 +58,7 @@ export function is_filtering_inactives(): boolean { return filter_out_inactives; } -export function has_recent_activity(sub: StreamSubscription): boolean { +export let has_recent_activity = (sub: StreamSubscription): boolean => { if (!filter_out_inactives || sub.pin_to_top) { // If users don't want to filter inactive streams // to the bottom, we respect that setting and don't @@ -70,6 +70,10 @@ export function has_recent_activity(sub: StreamSubscription): boolean { return true; } return stream_topic_history.stream_has_topics(sub.stream_id) || sub.newly_subscribed; +}; + +export function rewire_has_recent_activity(value: typeof has_recent_activity): void { + has_recent_activity = value; } export function has_recent_activity_but_muted(sub: StreamSubscription): boolean { diff --git a/web/src/stream_topic_history.ts b/web/src/stream_topic_history.ts index 436160cfc1..288bb8ebe0 100644 --- a/web/src/stream_topic_history.ts +++ b/web/src/stream_topic_history.ts @@ -356,10 +356,14 @@ export function has_history_for(stream_id: number): boolean { return fetched_stream_ids.has(stream_id); } -export function get_recent_topic_names(stream_id: number): string[] { +export let get_recent_topic_names = (stream_id: number): string[] => { const history = find_or_create(stream_id); return history.get_recent_topic_names(); +}; + +export function rewire_get_recent_topic_names(value: typeof get_recent_topic_names): void { + get_recent_topic_names = value; } export function get_max_message_id(stream_id: number): number { diff --git a/web/src/sub_store.ts b/web/src/sub_store.ts index 9479f702a7..ffb6d51573 100644 --- a/web/src/sub_store.ts +++ b/web/src/sub_store.ts @@ -33,8 +33,11 @@ export type StreamSubscription = z.infer; const subs_by_stream_id = new Map(); -export function get(stream_id: number): StreamSubscription | undefined { - return subs_by_stream_id.get(stream_id); +export let get = (stream_id: number): StreamSubscription | undefined => + subs_by_stream_id.get(stream_id); + +export function rewire_get(value: typeof get): void { + get = value; } export function validate_stream_ids(stream_ids: number[]): number[] { diff --git a/web/src/timerender.ts b/web/src/timerender.ts index 162a001ebd..177bbd619f 100644 --- a/web/src/timerender.ts +++ b/web/src/timerender.ts @@ -175,7 +175,7 @@ export type TimeRender = { needs_update: boolean; }; -export function render_now(time: Date, today = new Date(), display_year?: boolean): TimeRender { +export let render_now = (time: Date, today = new Date(), display_year?: boolean): TimeRender => { let time_str = ""; let needs_update = false; // render formal time to be used for tippy tooltip @@ -210,6 +210,10 @@ export function render_now(time: Date, today = new Date(), display_year?: boolea formal_time_str, needs_update, }; +}; + +export function rewire_render_now(value: typeof render_now): void { + render_now = value; } // Relative time rendering for use in most screens like Recent conversations. diff --git a/web/src/typeahead_helper.ts b/web/src/typeahead_helper.ts index ef3fa6647a..2a12e1bed7 100644 --- a/web/src/typeahead_helper.ts +++ b/web/src/typeahead_helper.ts @@ -94,7 +94,7 @@ type StreamData = { subscribed: boolean; }; -export function render_typeahead_item(args: { +export let render_typeahead_item = (args: { primary?: string | undefined; is_person?: boolean; img_src?: string; @@ -105,7 +105,7 @@ export function render_typeahead_item(args: { is_user_group?: boolean; stream?: StreamData; emoji_code?: string | undefined; -}): string { +}): string => { const has_image = args.img_src !== undefined; const has_status = args.status_emoji_info !== undefined; const has_secondary = args.secondary !== undefined; @@ -119,9 +119,13 @@ export function render_typeahead_item(args: { has_secondary_html, has_pronouns, }); +}; + +export function rewire_render_typeahead_item(value: typeof render_typeahead_item): void { + render_typeahead_item = value; } -export function render_person(person: UserPillData | UserOrMentionPillData): string { +export let render_person = (person: UserPillData | UserOrMentionPillData): string => { if (person.type === "broadcast") { return render_typeahead_item({ primary: person.user.special_item_text, @@ -155,14 +159,21 @@ export function render_person(person: UserPillData | UserOrMentionPillData): str }; return render_typeahead_item(typeahead_arguments); +}; + +export function rewire_render_person(value: typeof render_person): void { + render_person = value; } -export function render_user_group(user_group: {name: string; description: string}): string { - return render_typeahead_item({ +export let render_user_group = (user_group: {name: string; description: string}): string => + render_typeahead_item({ primary: user_groups.get_display_group_name(user_group.name), secondary: user_group.description, is_user_group: true, }); + +export function rewire_render_user_group(value: typeof render_user_group): void { + render_user_group = value; } export function render_person_or_user_group( @@ -175,14 +186,17 @@ export function render_person_or_user_group( return render_person(item); } -export function render_stream(stream: StreamData): string { - return render_typeahead_item({ +export let render_stream = (stream: StreamData): string => + render_typeahead_item({ secondary_html: stream.rendered_description, stream, }); + +export function rewire_render_stream(value: typeof render_stream): void { + render_stream = value; } -export function render_emoji(item: EmojiSuggestion): string { +export let render_emoji = (item: EmojiSuggestion): string => { const args = { is_emoji: true, primary: item.emoji_name.replaceAll("_", " "), @@ -198,6 +212,10 @@ export function render_emoji(item: EmojiSuggestion): string { ...args, emoji_code: item.emoji_code, }); +}; + +export function rewire_render_emoji(value: typeof render_emoji): void { + render_emoji = value; } export function sorter(query: string, objs: T[], get_item: (x: T) => string): T[] { @@ -415,7 +433,7 @@ export function sort_languages(matches: LanguageSuggestion[], query: string): La })); } -export function sort_recipients({ +export let sort_recipients = ({ users, query, current_stream_id, @@ -429,7 +447,7 @@ export function sort_recipients { function sort_relevance(items: UserType[]): UserType[] { return sort_people_for_relevance(items, current_stream_id, current_topic); } @@ -556,6 +574,10 @@ export function sort_recipients { function sort_group_setting_items( objs: (UserPillData | UserGroupPillData)[], ): (UserPillData | UserGroupPillData)[] { @@ -703,6 +725,10 @@ export function sort_group_setting_options({ } return setting_options.slice(0, MAX_ITEMS); +}; + +export function rewire_sort_group_setting_options(value: typeof sort_group_setting_options): void { + sort_group_setting_options = value; } type SlashCommand = { @@ -781,7 +807,7 @@ function compare_by_name(stream_a: StreamSubscription, stream_b: StreamSubscript return util.strcmp(stream_a.name, stream_b.name); } -export function sort_streams(matches: StreamPillData[], query: string): StreamPillData[] { +export let sort_streams = (matches: StreamPillData[], query: string): StreamPillData[] => { const name_results = typeahead.triage(query, matches, (x) => x.name, compare_by_activity); const desc_results = typeahead.triage( query, @@ -791,11 +817,19 @@ export function sort_streams(matches: StreamPillData[], query: string): StreamPi ); return [...name_results.matches, ...desc_results.matches, ...desc_results.rest]; +}; + +export function rewire_sort_streams(value: typeof sort_streams): void { + sort_streams = value; } -export function sort_streams_by_name(matches: StreamPillData[], query: string): StreamPillData[] { +export let sort_streams_by_name = (matches: StreamPillData[], query: string): StreamPillData[] => { const results = typeahead.triage(query, matches, (x) => x.name, compare_by_name); return [...results.matches, ...results.rest]; +}; + +export function rewire_sort_streams_by_name(value: typeof sort_streams_by_name): void { + sort_streams_by_name = value; } export function query_matches_person( diff --git a/web/src/ui_util.ts b/web/src/ui_util.ts index bb71cbddf5..78574b67f7 100644 --- a/web/src/ui_util.ts +++ b/web/src/ui_util.ts @@ -72,10 +72,15 @@ export function is_user_said_paragraph($element: JQuery): boolean { return remaining_text.trim() === ":"; } -export function get_collapsible_status_array($elements: JQuery): boolean[] { - return [...$elements].map( +export let get_collapsible_status_array = ($elements: JQuery): boolean[] => + [...$elements].map( (element) => $(element).is("blockquote") || is_user_said_paragraph($(element)), ); + +export function rewire_get_collapsible_status_array( + value: typeof get_collapsible_status_array, +): void { + get_collapsible_status_array = value; } export function potentially_collapse_quotes($element: JQuery): boolean { diff --git a/web/src/upload.ts b/web/src/upload.ts index 06f49cc2ea..95ab4bf2fa 100644 --- a/web/src/upload.ts +++ b/web/src/upload.ts @@ -125,7 +125,7 @@ export function edit_config(row: number): Config { }; } -export function hide_upload_banner(uppy: Uppy, config: Config, file_id: string): void { +export let hide_upload_banner = (uppy: Uppy, config: Config, file_id: string): void => { config.upload_banner(file_id).remove(); if (uppy.getFiles().length === 0) { if (config.mode === "compose") { @@ -134,6 +134,10 @@ export function hide_upload_banner(uppy: Uppy, config: Config, file_id: string): config.send_button().prop("disabled", false); } } +}; + +export function rewire_hide_upload_banner(value: typeof hide_upload_banner): void { + hide_upload_banner = value; } function add_upload_banner( @@ -172,7 +176,7 @@ export function show_error_message( } } -export function upload_files(uppy: Uppy, config: Config, files: File[] | FileList): void { +export let upload_files = (uppy: Uppy, config: Config, files: File[] | FileList): void => { if (files.length === 0) { return; } @@ -230,6 +234,7 @@ export function upload_files(uppy: Uppy, config: Config, files: File[] | FileLis file_id, true, ); + // eslint-disable-next-line no-loop-func config.upload_banner_cancel_button(file_id).one("click", () => { compose_ui.replace_syntax(get_translated_status(file), "", config.textarea()); compose_ui.autosize_textarea(config.textarea()); @@ -238,10 +243,15 @@ export function upload_files(uppy: Uppy, config: Config, files: File[] | FileLis uppy.removeFile(file_id); hide_upload_banner(uppy, config, file_id); }); + // eslint-disable-next-line no-loop-func config.upload_banner_hide_button(file_id).one("click", () => { hide_upload_banner(uppy, config, file_id); }); } +}; + +export function rewire_upload_files(value: typeof upload_files): void { + upload_files = value; } export function setup_upload(config: Config): Uppy { diff --git a/web/src/user_topics.ts b/web/src/user_topics.ts index 4d7575599d..d1f74fdce4 100644 --- a/web/src/user_topics.ts +++ b/web/src/user_topics.ts @@ -116,7 +116,7 @@ export function get_user_topics_for_visibility_policy(visibility_policy: number) return topics; } -export function set_user_topic_visibility_policy( +export let set_user_topic_visibility_policy = ( stream_id: number, topic: string, visibility_policy: number, @@ -125,7 +125,7 @@ export function set_user_topic_visibility_policy( $status_element?: JQuery, success_cb?: () => void, error_cb?: () => void, -): void { +): void => { const data = { stream_id, topic, @@ -203,6 +203,12 @@ export function set_user_topic_visibility_policy( } }, }); +}; + +export function rewire_set_user_topic_visibility_policy( + value: typeof set_user_topic_visibility_policy, +): void { + set_user_topic_visibility_policy = value; } export function set_visibility_policy_for_element($elt: JQuery, visibility_policy: number): void { diff --git a/web/src/util.ts b/web/src/util.ts index eaacb89808..4daa69ede6 100644 --- a/web/src/util.ts +++ b/web/src/util.ts @@ -315,7 +315,7 @@ export function get_time_from_date_muted(date_muted: number | undefined): number return date_muted * 1000; } -export function call_function_periodically(callback: () => void, delay: number): void { +export let call_function_periodically = (callback: () => void, delay: number): void => { // We previously used setInterval for this purpose, but // empirically observed that after unsuspend, Chrome can end // up trying to "catch up" by doing dozens of these requests @@ -337,6 +337,10 @@ export function call_function_periodically(callback: () => void, delay: number): // exception. callback(); }, delay); +}; + +export function rewire_call_function_periodically(value: typeof call_function_periodically): void { + call_function_periodically = value; } export function get_string_diff(string1: string, string2: string): [number, number, number] {