web: Add setters for rewired variables.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2024-11-08 02:33:34 -08:00 committed by Tim Abbott
parent e2cc125583
commit 52e59a9605
46 changed files with 599 additions and 133 deletions

View File

@ -55,19 +55,27 @@ function same_recipient(a: Recipient | null, b: Recipient | null): boolean {
/** Exported only for tests. */ /** Exported only for tests. */
export let state: TypingStatusState | null = null; export let state: TypingStatusState | null = null;
export function rewire_state(value: typeof state): void {
state = value;
}
/** Exported only for tests. */ /** 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."); assert(state !== null, "State object should not be null here.");
clearTimeout(state.idle_timer); clearTimeout(state.idle_timer);
worker.notify_server_stop(state.current_recipient); worker.notify_server_stop(state.current_recipient);
state = null; state = null;
};
export function rewire_stop_last_notification(value: typeof stop_last_notification): void {
stop_last_notification = value;
} }
/** Exported only for tests. */ /** Exported only for tests. */
export function start_or_extend_idle_timer( export let start_or_extend_idle_timer = (
worker: TypingStatusWorker, worker: TypingStatusWorker,
typing_stopped_wait_period: number, typing_stopped_wait_period: number,
): ReturnType<typeof setTimeout> { ): ReturnType<typeof setTimeout> => {
function on_idle_timeout(): void { function on_idle_timeout(): void {
// We don't do any real error checking here, because // We don't do any real error checking here, because
// if we've been idle, we need to tell folks, and if // 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); clearTimeout(state.idle_timer);
} }
return setTimeout(on_idle_timeout, typing_stopped_wait_period); 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 { 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 // Exported for tests
export function actually_ping_server( export let actually_ping_server = (
worker: TypingStatusWorker, worker: TypingStatusWorker,
recipient: Recipient, recipient: Recipient,
current_time: number, current_time: number,
typing_started_wait_period: number, typing_started_wait_period: number,
): void { ): void => {
worker.notify_server_start(recipient); worker.notify_server_start(recipient);
set_next_start_time(current_time, typing_started_wait_period); 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. */ /** Exported only for tests. */
export function maybe_ping_server( export let maybe_ping_server = (
worker: TypingStatusWorker, worker: TypingStatusWorker,
recipient: Recipient, recipient: Recipient,
typing_started_wait_period: number, typing_started_wait_period: number,
): void { ): void => {
assert(state !== null, "State object should not be null here."); assert(state !== null, "State object should not be null here.");
const current_time = worker.get_current_time(); const current_time = worker.get_current_time();
if (current_time > state.next_send_start_time) { if (current_time > state.next_send_start_time) {
actually_ping_server(worker, recipient, current_time, typing_started_wait_period); 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;
} }
/** /**

View File

@ -114,7 +114,7 @@ export function compute_active_status(): ActivityState {
return ActivityState.IDLE; 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: // Zulip has 2 data feeds coming from the server to the client:
// The server_events data, and this presence feed. Data from // The server_events data, and this presence feed. Data from
// server_events is nicely serialized, but if we've been offline // 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 { export function mark_client_active(): void {

View File

@ -59,7 +59,7 @@ export function clear_for_testing(): void {
user_filter = undefined; user_filter = undefined;
} }
export function update_presence_indicators(): void { export let update_presence_indicators = (): void => {
$("[data-presence-indicator-user-id]").each(function () { $("[data-presence-indicator-user-id]").each(function () {
const user_id = Number.parseInt($(this).attr("data-presence-indicator-user-id") ?? "", 10); const user_id = Number.parseInt($(this).attr("data-presence-indicator-user-id") ?? "", 10);
assert(!Number.isNaN(user_id)); 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") .removeClass("user_circle_empty user_circle_green user_circle_idle")
.addClass(user_circle_class); .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 { 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)); $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) { if (realm.realm_presence_disabled) {
return undefined; 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); render_empty_user_list_message_if_needed(buddy_list.$other_users_list);
return all_user_ids; // for testing 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 { function do_update_users_for_search(): void {

View File

@ -12,7 +12,7 @@ import * as ui_report from "./ui_report";
export let loaded = false; export let loaded = false;
export function rerender_alert_words_ui(): void { export let rerender_alert_words_ui = (): void => {
if (!loaded) { if (!loaded) {
return; return;
} }
@ -33,6 +33,10 @@ export function rerender_alert_words_ui(): void {
...ListWidget.generic_sort_functions("alphabetic", ["word"]), ...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 { function update_alert_word_status(status_text: string, is_error: boolean): void {

View File

@ -1,7 +1,11 @@
import {Filter} from "./filter"; import {Filter} from "./filter";
import {MessageListData} from "./message_list_data"; import {MessageListData} from "./message_list_data";
export const all_messages_data = new MessageListData({ export let all_messages_data = new MessageListData({
excludes_muted_topics: false, excludes_muted_topics: false,
filter: new Filter([]), filter: new Filter([]),
}); });
export function rewire_all_messages_data(value: typeof all_messages_data): void {
all_messages_data = value;
}

View File

@ -186,7 +186,11 @@ export function defaultSorter(items: string[], query: string): string[] {
return [...beginswith, ...caseSensitive, ...caseInsensitive]; 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 /* TYPEAHEAD PUBLIC CLASS DEFINITION
* ================================= */ * ================================= */

View File

@ -58,7 +58,7 @@ export function save_old_hash(): boolean {
return was_internal_change; return was_internal_change;
} }
export function update(new_hash: string): void { export let update = (new_hash: string): void => {
const old_hash = window.location.hash; const old_hash = window.location.hash;
if (!new_hash.startsWith("#")) { if (!new_hash.startsWith("#")) {
@ -78,6 +78,10 @@ export function update(new_hash: string): void {
state.old_hash = old_hash; state.old_hash = old_hash;
state.is_internal_change = true; state.is_internal_change = true;
window.location.hash = new_hash; window.location.hash = new_hash;
};
export function rewire_update(value: typeof update): void {
update = value;
} }
export function exit_overlay(): void { export function exit_overlay(): void {

View File

@ -27,8 +27,19 @@ import * as util from "./util";
*/ */
export const max_size_before_shrinking = 600; export let max_size_before_shrinking = 600;
export const max_channel_size_to_show_all_subscribers = 75;
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; 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, user_id: number,
pm_ids: Set<number>, pm_ids: Set<number>,
stream_id?: number | null, stream_id?: number | null,
): boolean { ): boolean => {
if (stream_id) { if (stream_id) {
return stream_data.is_user_subscribed(stream_id, user_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 pm_ids.has(user_id) || people.is_my_user_id(user_id);
} }
return false; return false;
};
export function rewire_user_matches_narrow(value: typeof user_matches_narrow): void {
user_matches_narrow = value;
} }
export function compare_function( export function compare_function(

View File

@ -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_recipient_edited_manually(false);
compose_state.set_is_content_unedited_restored_draft(false); compose_state.set_is_content_unedited_restored_draft(false);
if (request.type === "private") { if (request.type === "private") {
@ -269,6 +269,10 @@ export function send_message(request = create_message_object()) {
// taking a longtime to send. // taking a longtime to send.
setTimeout(() => echo.display_slow_send_loading_spinner(message), 5000); 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) { 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 // Common entrypoint for asking the server to send the message
// currently drafted in the compose box, including for scheduled // currently drafted in the compose box, including for scheduled
// messages. // messages.
export function finish(scheduling_message = false) { export let finish = (scheduling_message = false) => {
if (compose_ui.compose_spinner_visible) { if (compose_ui.compose_spinner_visible) {
// Avoid sending a message twice in parallel in races where // Avoid sending a message twice in parallel in races where
// the user clicks the `Send` button very quickly twice or // the user clicks the `Send` button very quickly twice or
@ -326,6 +330,10 @@ export function finish(scheduling_message = false) {
} }
do_post_send_tasks(); do_post_send_tasks();
return true; return true;
};
export function rewire_finish(value) {
finish = value;
} }
export function do_post_send_tasks() { export function do_post_send_tasks() {

View File

@ -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"); $(".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 { function hide_box(): void {
@ -109,8 +113,12 @@ function show_compose_box(opts: ComposeActionsOpts): void {
compose_ui.set_focus(opts_by_message_type); 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(""); $("#compose").find("input[type=text], textarea").val("");
};
export function rewire_clear_textarea(value: typeof clear_textarea): void {
clear_textarea = value;
} }
function clear_box(): void { function clear_box(): void {
@ -135,7 +143,7 @@ function clear_box(): void {
} }
let autosize_callback_opts: ComposeActionsStartOpts; 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()) { if (!compose_ui.is_expanded()) {
autosize_callback_opts = opts; autosize_callback_opts = opts;
let has_resized_once = false; let has_resized_once = false;
@ -157,15 +165,23 @@ export function autosize_message_content(opts: ComposeActionsStartOpts): void {
}); });
autosize($("textarea#compose-textarea")); 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_close").attr("data-tooltip-template-id", "compose_close_tooltip_template");
$("#compose_controls").hide(); $("#compose_controls").hide();
$(".message_comp").show(); $(".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 // This is sort of a kitchen sink function, and it's called only
// by compose.start() for now. Having this as a separate function // by compose.start() for now. Having this as a separate function
// makes testing a bit easier. // makes testing a bit easier.
@ -182,6 +198,10 @@ export function complete_starting_tasks(opts: ComposeActionsOpts): void {
if (!narrow_state.narrowed_by_reply()) { if (!narrow_state.narrowed_by_reply()) {
compose_notifications.maybe_show_one_time_interleaved_view_messages_fading_banner(); 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 { 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) { if (page_params.is_spectator) {
spectators.login_to_access(); spectators.login_to_access();
return; return;
@ -390,9 +410,13 @@ export function start(raw_opts: ComposeActionsStartOpts): void {
resize.reset_compose_message_max_height(); resize.reset_compose_message_max_height();
complete_starting_tasks(opts); 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 // As user closes the compose box, restore the compose box max height
if (compose_ui.is_expanded()) { if (compose_ui.is_expanded()) {
compose_ui.make_compose_box_original_size(); compose_ui.make_compose_box_original_size();
@ -420,6 +444,10 @@ export function cancel(): void {
compose_state.set_message_type(undefined); compose_state.set_message_type(undefined);
compose_pm_pill.clear(); compose_pm_pill.clear();
$(document).trigger("compose_canceled.zulip"); $(document).trigger("compose_canceled.zulip");
};
export function rewire_cancel(value: typeof cancel): void {
cancel = value;
} }
export function on_show_navigation_view(): void { export function on_show_navigation_view(): void {
@ -440,7 +468,7 @@ export function on_show_navigation_view(): void {
cancel(); cancel();
} }
export function on_topic_narrow(): void { export let on_topic_narrow = (): void => {
if (!compose_state.composing()) { if (!compose_state.composing()) {
// If our compose box is closed, then just // If our compose box is closed, then just
// leave it closed, assuming that the user is // leave it closed, assuming that the user is
@ -491,6 +519,10 @@ export function on_topic_narrow(): void {
compose_fade.update_message_list(); compose_fade.update_message_list();
drafts.update_compose_draft_count(); drafts.update_compose_draft_count();
$("textarea#compose-textarea").trigger("focus"); $("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. // TODO/typescript: Fill this in when converting narrow.js to typescripot.

View File

@ -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, include_unmute_banner = true,
skip_automatic_new_visibility_policy_banner = false, skip_automatic_new_visibility_policy_banner = false,
): void { ): void => {
for (const classname of Object.values(MESSAGE_SENT_CLASSNAMES)) { for (const classname of Object.values(MESSAGE_SENT_CLASSNAMES)) {
if ( if (
skip_automatic_new_visibility_policy_banner && skip_automatic_new_visibility_policy_banner &&
@ -115,6 +115,10 @@ export function clear_message_sent_banners(
clear_unmute_topic_notifications(); clear_unmute_topic_notifications();
} }
scroll_to_message_banner_message_id = null; 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. // TODO: Replace with compose_ui.hide_compose_spinner() when it is converted to ts.

View File

@ -61,7 +61,7 @@ export function get_recipient_label(message?: ComposeClosedMessage): string {
} }
// Exported for tests // 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); $(".compose_reply_button").attr("disabled", disable ? "disabled" : null);
if (disable) { if (disable) {
$("#compose_buttons .compose-reply-button-wrapper").attr( $("#compose_buttons .compose-reply-button-wrapper").attr(
@ -81,6 +81,10 @@ export function update_reply_button_state(disable = false): void {
"selected_conversation", "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 { function update_buttons(disable_reply?: boolean): void {

View File

@ -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" // value is something like "alice@example.com,bob@example.com"
clear(); clear();
widget.appendValue(value); 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[] { export function get_user_ids(): number[] {
@ -84,11 +88,15 @@ export function get_user_ids_string(): string {
return user_ids_string; return user_ids_string;
} }
export function get_emails(): string { export let get_emails = (): string => {
// return something like "alice@example.com,bob@example.com" // return something like "alice@example.com,bob@example.com"
const user_ids = get_user_ids(); const user_ids = get_user_ids();
const emails = user_ids.map((id) => people.get_by_user_id(id).email).join(","); const emails = user_ids.map((id) => people.get_by_user_id(id).email).join(",");
return emails; return emails;
};
export function rewire_get_emails(value: typeof get_emails): void {
get_emails = value;
} }
export function filter_taken_users(persons: User[]): User[] { export function filter_taken_users(persons: User[]): User[] {

View File

@ -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(); const message_type = compose_state.get_message_type();
if (message_type === "stream") { if (message_type === "stream") {
const stream_exists = Boolean(compose_state.stream_id()); 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); $(".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 { function update_fade(): void {
@ -131,7 +137,7 @@ export function get_posting_policy_error_message(): string {
return ""; 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(); const banner_text = get_posting_policy_error_message();
if (banner_text === "") { if (banner_text === "") {
compose_validate.set_recipient_disallowed(false); compose_validate.set_recipient_disallowed(false);
@ -147,6 +153,12 @@ export function check_posting_policy_for_compose_box(): void {
} else { } else {
compose_banner.show_error_message(banner_text, banner_classname, $("#compose_banners")); 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 { 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(); 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(); const prev_message_type = compose_state.get_message_type();
let curr_message_type: MessageType = "stream"; let curr_message_type: MessageType = "stream";
@ -226,6 +238,12 @@ export function on_compose_select_recipient_update(): void {
} }
update_on_recipient_change(); 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 { 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<HTMLTextAreaElement> = $("textarea#compose-textarea"); const $textarea: JQuery<HTMLTextAreaElement> = $("textarea#compose-textarea");
// Change compose placeholder text only if compose box is open. // Change compose placeholder text only if compose box is open.
if (!$textarea.is(":visible")) { if (!$textarea.is(":visible")) {
@ -352,4 +370,8 @@ export function update_placeholder_text(): void {
$textarea.attr("placeholder", placeholder); $textarea.attr("placeholder", placeholder);
compose_ui.autosize_textarea($textarea); compose_ui.autosize_textarea($textarea);
};
export function rewire_update_placeholder_text(value: typeof update_placeholder_text): void {
update_placeholder_text = value;
} }

View File

@ -22,12 +22,12 @@ import * as recent_view_util from "./recent_view_util";
import * as stream_data from "./stream_data"; import * as stream_data from "./stream_data";
import * as unread_ops from "./unread_ops"; import * as unread_ops from "./unread_ops";
export function respond_to_message(opts: { export let respond_to_message = (opts: {
keep_composebox_empty?: boolean; keep_composebox_empty?: boolean;
message_id?: number; message_id?: number;
reply_type?: "personal"; reply_type?: "personal";
trigger?: string; trigger?: string;
}): void { }): void => {
let message; let message;
let msg_type: "private" | "stream"; let msg_type: "private" | "stream";
if (recent_view_util.is_visible()) { if (recent_view_util.is_visible()) {
@ -147,6 +147,10 @@ export function respond_to_message(opts: {
is_reply: true, is_reply: true,
keep_composebox_empty: opts.keep_composebox_empty, 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: { export function reply_with_mention(opts: {
@ -166,7 +170,9 @@ export function reply_with_mention(opts: {
compose_ui.insert_syntax_and_focus(mention); 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, // Returns the message_id if the selection is entirely within a message,
// otherwise returns undefined. // otherwise returns undefined.
assert(selection !== null); assert(selection !== null);
@ -178,6 +184,12 @@ export function selection_within_message_id(selection = window.getSelection()):
return start_id; return start_id;
} }
return undefined; 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}): { function get_quote_target(opts: {message_id?: number; quote_content?: string}): {

View File

@ -106,12 +106,16 @@ export function stream_id(): number | undefined {
return undefined; return undefined;
} }
export function stream_name(): string { export let stream_name = (): string => {
const stream_id = selected_recipient_id; const stream_id = selected_recipient_id;
if (typeof stream_id === "number") { if (typeof stream_id === "number") {
return sub_store.maybe_get_stream_name(stream_id) ?? ""; return sub_store.maybe_get_stream_name(stream_id) ?? "";
} }
return ""; return "";
};
export function rewire_stream_name(value: typeof stream_name): void {
stream_name = value;
} }
export function set_stream_id(stream_id: number | ""): void { export function set_stream_id(stream_id: number | ""): void {

View File

@ -70,6 +70,11 @@ const message_render_response_schema = z.object({
}); });
export let compose_spinner_visible = false; 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 shift_pressed = false; // true or false
export let code_formatting_button_triggered = false; // true or false export let code_formatting_button_triggered = false; // true or false
export let compose_textarea_typeahead: Typeahead<TypeaheadSuggestion> | undefined; export let compose_textarea_typeahead: Typeahead<TypeaheadSuggestion> | undefined;
@ -102,20 +107,24 @@ export function is_full_size(): boolean {
return full_size_status; return full_size_status;
} }
export function autosize_textarea($textarea: JQuery<HTMLTextAreaElement>): void { export let autosize_textarea = ($textarea: JQuery<HTMLTextAreaElement>): void => {
// Since this supports both compose and file upload, one must pass // Since this supports both compose and file upload, one must pass
// in the text area to autosize. // in the text area to autosize.
if (!is_expanded()) { if (!is_expanded()) {
autosize.update($textarea); 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, content: string,
$textarea: JQuery<HTMLTextAreaElement>, $textarea: JQuery<HTMLTextAreaElement>,
replace_all = false, replace_all = false,
replace_all_without_undo_support = false, replace_all_without_undo_support = false,
): void { ): void => {
if (replace_all_without_undo_support) { if (replace_all_without_undo_support) {
// setFieldText is very slow and noticeable when inserting 10k+ // setFieldText is very slow and noticeable when inserting 10k+
// characters of text like from a drafted response, // characters of text like from a drafted response,
@ -132,6 +141,12 @@ export function insert_and_scroll_into_view(
$textarea.trigger("blur"); $textarea.trigger("blur");
$textarea.trigger("focus"); $textarea.trigger("focus");
autosize_textarea($textarea); 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 { 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<HTMLTextAreaElement>, syntax: string): void { export let smart_insert_inline = ($textarea: JQuery<HTMLTextAreaElement>, syntax: string): void => {
function is_space(c: string | undefined): boolean { function is_space(c: string | undefined): boolean {
return c === " " || c === "\t" || c === "\n"; return c === " " || c === "\t" || c === "\n";
} }
@ -200,6 +215,10 @@ export function smart_insert_inline($textarea: JQuery<HTMLTextAreaElement>, synt
} }
insert_and_scroll_into_view(syntax, $textarea); 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( export function smart_insert_block(
@ -252,12 +271,12 @@ export function smart_insert_block(
insert_and_scroll_into_view(syntax, $textarea); insert_and_scroll_into_view(syntax, $textarea);
} }
export function insert_syntax_and_focus( export let insert_syntax_and_focus = (
syntax: string, syntax: string,
$textarea = $<HTMLTextAreaElement>("textarea#compose-textarea"), $textarea = $<HTMLTextAreaElement>("textarea#compose-textarea"),
mode = "inline", mode = "inline",
padding_newlines?: number, padding_newlines?: number,
): void { ): void => {
// Generic helper for inserting syntax into the main compose box // Generic helper for inserting syntax into the main compose box
// where the cursor was and focusing the area. Mostly a thin // where the cursor was and focusing the area. Mostly a thin
// wrapper around smart_insert_inline and smart_inline_block. // wrapper around smart_insert_inline and smart_inline_block.
@ -277,13 +296,17 @@ export function insert_syntax_and_focus(
} else if (mode === "block") { } else if (mode === "block") {
smart_insert_block($textarea, syntax, padding_newlines); 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, old_syntax: string,
new_syntax: string, new_syntax: string,
$textarea = $<HTMLTextAreaElement>("textarea#compose-textarea"), $textarea = $<HTMLTextAreaElement>("textarea#compose-textarea"),
): boolean { ): boolean => {
// The following couple lines are needed to later restore the initial // The following couple lines are needed to later restore the initial
// logical position of the cursor after the replacement // logical position of the cursor after the replacement
const prev_caret = $textarea.caret(); const prev_caret = $textarea.caret();
@ -322,6 +345,10 @@ export function replace_syntax(
// Return if anything was actually replaced. // Return if anything was actually replaced.
return old_text !== new_text; 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 { export function compute_placeholder_text(opts: ComposePlaceholderOptions): string {
@ -374,7 +401,7 @@ export function compute_placeholder_text(opts: ComposePlaceholderOptions): strin
return DEFAULT_COMPOSE_PLACEHOLDER; 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) { if (set_top) {
// As `#compose` has `position: fixed` property, we cannot // As `#compose` has `position: fixed` property, we cannot
// make the compose-box to attain the correct height just by // 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 { } else {
$("#compose").css("top", ""); $("#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 { 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)); return [...code_blocks].some((code_block) => code_block?.textContent?.includes(unique_insert));
} }
export function format_text( export let format_text = (
$textarea: JQuery<HTMLTextAreaElement>, $textarea: JQuery<HTMLTextAreaElement>,
type: string, type: string,
inserted_content = "", inserted_content = "",
): void { ): void => {
const italic_syntax = "*"; const italic_syntax = "*";
const bold_syntax = "**"; const bold_syntax = "**";
const bold_and_italic_syntax = "***"; const bold_and_italic_syntax = "***";
@ -1156,6 +1187,10 @@ export function format_text(
break; 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 /* TODO: This functions don't belong in this module, as they have

View File

@ -476,7 +476,7 @@ function is_recipient_large_topic(): boolean {
} }
// Exported for tests // Exported for tests
export function wildcard_mention_policy_authorizes_user(): boolean { export let wildcard_mention_policy_authorizes_user = (): boolean => {
if ( if (
realm.realm_wildcard_mention_policy === realm.realm_wildcard_mention_policy ===
settings_config.wildcard_mention_policy_values.by_everyone.code 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 days >= realm.realm_waiting_period_threshold && !current_user.is_guest;
} }
return !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 { export function stream_wildcard_mention_allowed(): boolean {

View File

@ -106,7 +106,11 @@ export type TypeaheadSuggestion =
| SlashCommandSuggestion; | SlashCommandSuggestion;
// We export it to allow tests to mock it. // 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[] = []; 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(); const cursor = $input.caret();
return [query.slice(0, cursor), query.slice(cursor)]; 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 { export function tokenize_compose_str(s: string): string {

View File

@ -22,9 +22,13 @@ import * as timerender from "./timerender";
import * as ui_util from "./ui_util"; import * as ui_util from "./ui_util";
import * as util from "./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"); const $drafts_li = $(".top_left_drafts");
ui_util.update_unread_count_in_dom($drafts_li, count); 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 { 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_container = $(".compose-drafts-count-container");
const $count_ele = $count_container.find(".compose-drafts-count"); const $count_ele = $count_container.find(".compose-drafts-count");
if (!compose_state.has_full_recipient()) { if (!compose_state.has_full_recipient()) {
@ -235,11 +239,19 @@ export function update_compose_draft_count(): void {
$count_ele.text(""); $count_ele.text("");
$count_container.hide(); $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(); const drafts = draft_model.get();
set_count(Object.keys(drafts).length); 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 { export function delete_all_drafts(): void {
@ -396,7 +408,7 @@ type UpdateDraftOptions = {
is_sending_saving?: boolean; 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 draft_id = compose_draft_id;
const old_draft = draft_id === undefined ? undefined : draft_model.getDraft(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); maybe_notify(no_notify);
return new_draft_id; return new_draft_id;
};
export function rewire_update_draft(value: typeof update_draft): void {
update_draft = value;
} }
export const DRAFT_LIFETIME = 30; export const DRAFT_LIFETIME = 30;

View File

@ -288,14 +288,14 @@ export function is_slash_command(content: string): boolean {
return !content.startsWith("/me") && content.startsWith("/"); return !content.startsWith("/me") && content.startsWith("/");
} }
export function try_deliver_locally( export let try_deliver_locally = (
message_request: MessageRequest, message_request: MessageRequest,
insert_new_messages: ( insert_new_messages: (
messages: LocalMessage[], messages: LocalMessage[],
send_by_this_client: boolean, send_by_this_client: boolean,
deliver_locally: boolean, deliver_locally: boolean,
) => Message[], ) => Message[],
): Message | undefined { ): Message | undefined => {
// Checks if the message request can be locally echoed, and if so, // Checks if the message request can be locally echoed, and if so,
// adds a local echoed copy of the message to appropriate message lists. // 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); const message = insert_local_message(message_request, local_id_float, insert_new_messages);
return message; 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 { export function edit_locally(message: Message, request: LocalEditRequest): Message {
@ -432,7 +436,7 @@ export function edit_locally(message: Message, request: LocalEditRequest): Messa
return message; 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); const message = echo_state.get_message_waiting_for_id(local_id);
echo_state.remove_message_from_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, 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 { 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; 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 // Error sending message, show inline
const message = message_store.get(message_id)!; const message = message_store.get(message_id)!;
message.failed_request = true; message.failed_request = true;
message.show_slow_send_spinner = false; message.show_slow_send_spinner = false;
show_message_failed(message_id, error_response); 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 { function abort_message(message: Message): void {

View File

@ -250,7 +250,7 @@ export function get_keypress_hotkey(e) {
return keypress_mappings[e.which]; return keypress_mappings[e.which];
} }
export function processing_text() { export let processing_text = () => {
const $focused_elt = $(":focus"); const $focused_elt = $(":focus");
return ( return (
$focused_elt.is("input") || $focused_elt.is("input") ||
@ -260,6 +260,10 @@ export function processing_text() {
$focused_elt.attr("id") === "compose-send-button" || $focused_elt.attr("id") === "compose-send-button" ||
$focused_elt.parents(".dropdown-list-container").length >= 1 $focused_elt.parents(".dropdown-list-container").length >= 1
); );
};
export function rewire_processing_text(value) {
processing_text = value;
} }
export function in_content_editable_widget(e) { export function in_content_editable_widget(e) {

View File

@ -103,11 +103,11 @@ export function update_current_view_for_topic_visibility() {
return false; return false;
} }
export function update_views_filtered_on_message_property( export let update_views_filtered_on_message_property = (
message_ids, message_ids,
property_term_type, property_term_type,
property_value, property_value,
) { ) => {
// NOTE: Call this function after updating the message property locally. // NOTE: Call this function after updating the message property locally.
assert(!property_term_type.includes("not-")); 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 // can be used to update other message lists and
// cached message data structures as well. // cached message data structures as well.
}, },
// eslint-disable-next-line no-loop-func
success(data) { success(data) {
// `messages_to_fetch` might already be cached locally when // `messages_to_fetch` might already be cached locally when
// we reach here but `message_helper.process_new_message` // 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) { export function insert_new_messages(messages, sent_by_this_client, deliver_locally) {

View File

@ -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 () { export const send_read = (function () {
let queue: Message[] = []; let queue: Message[] = [];

View File

@ -351,7 +351,7 @@ type ShowMessageViewOpts = {
show_more_topics?: boolean; 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. /* Main entry point for switching to a new view / message list.
Supported parameters: Supported parameters:
@ -777,6 +777,10 @@ export function show(raw_terms: NarrowTerm[], show_opts: ShowMessageViewOpts): v
resize.resize_stream_filters_container(); resize.resize_stream_filters_container();
}); });
}); });
};
export function rewire_show(value: typeof show): void {
show = value;
} }
function navigate_to_anchor_message(opts: { function navigate_to_anchor_message(opts: {

View File

@ -114,7 +114,7 @@ export function set_compose_defaults(): {
return opts; 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) { if (current_filter === undefined) {
return 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 Number.parseInt(stream_operands[0], 10);
} }
return undefined; 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 { 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]); 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) { if (current_filter === undefined) {
return undefined; return undefined;
} }
@ -157,6 +161,10 @@ export function topic(current_filter: Filter | undefined = filter()): string | u
return operands[0]; return operands[0];
} }
return undefined; return undefined;
};
export function rewire_topic(value: typeof topic): void {
topic = value;
} }
export function pm_ids_string(filter?: Filter): string | undefined { 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; return user_ids_string;
} }
export function pm_ids_set(filter?: Filter): Set<number> { export let pm_ids_set = (filter?: Filter): Set<number> => {
const ids_string = pm_ids_string(filter); const ids_string = pm_ids_string(filter);
const pm_ids_list = ids_string ? people.user_ids_string_to_ids_array(ids_string) : []; const pm_ids_list = ids_string ? people.user_ids_string_to_ids_array(ids_string) : [];
return new Set(pm_ids_list); 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( export function pm_emails_string(
@ -194,9 +206,9 @@ export function pm_emails_string(
return operands[0]; return operands[0];
} }
export function get_first_unread_info( export let get_first_unread_info = (
current_filter: Filter | undefined = filter(), 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"}; const cannot_compute_response: {flavor: "cannot_compute"} = {flavor: "cannot_compute"};
if (current_filter === undefined) { if (current_filter === undefined) {
// we don't yet support the all-messages view // we don't yet support the all-messages view
@ -231,11 +243,15 @@ export function get_first_unread_info(
flavor: "found", flavor: "found",
msg_id, 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(), current_filter: Filter | undefined = filter(),
): number[] | undefined { ): number[] | undefined => {
// This function currently only returns valid results for // This function currently only returns valid results for
// certain types of narrows, mostly left sidebar narrows. // certain types of narrows, mostly left sidebar narrows.
// For more complicated narrows we may return undefined. // For more complicated narrows we may return undefined.
@ -318,6 +334,12 @@ export function _possible_unread_message_ids(
} }
return undefined; 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 // Are we narrowed to direct messages: the direct message feed or a

View File

@ -69,7 +69,7 @@ export function potential_subscribers(stream_id: number): User[] {
return people.filter_all_users(is_potential_subscriber); 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) { if (include_bots) {
return get_user_set(stream_id).size; 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; 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[] { export function get_subscribers(stream_id: number): number[] {

View File

@ -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. // 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); const person = people_by_user_id_dict.get(user_id);
assert(person, `Unknown user_id in get_by_user_id: ${user_id}`); assert(person, `Unknown user_id in get_by_user_id: ${user_id}`);
return person; 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. // 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; 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); const person = people_dict.get(email);
if (!person) { if (!person) {
@ -137,6 +141,10 @@ export function get_by_email(email: string): User | undefined {
} }
return person; 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 { 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); 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( let user_ids = util.try_parse_as_truthy(
emails.map((email) => { emails.map((email) => {
const person = get_by_email(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); user_ids = sort_numerically(user_ids);
return user_ids.join(","); 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 { 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; return bot_ids;
} }
export function get_active_human_count(): number { export let get_active_human_count = (): number => {
let count = 0; let count = 0;
for (const person of active_user_dict.values()) { for (const person of active_user_dict.values()) {
if (!person.is_bot) { if (!person.is_bot) {
@ -1064,6 +1078,10 @@ export function get_active_human_count(): number {
} }
} }
return count; 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[] { export function get_active_user_ids(): number[] {

View File

@ -11,8 +11,12 @@ type PMConversation = {
const partners = new Set<number>(); const partners = new Set<number>();
export function set_partner(user_id: number): void { export let set_partner = (user_id: number): void => {
partners.add(user_id); 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 { export function is_partner(user_id: number): boolean {

View File

@ -1,6 +1,8 @@
import generated_pygments_data from "../generated/pygments_data.json"; import generated_pygments_data from "../generated/pygments_data.json";
type PygmentsLanguage = {priority: number; pretty_name: string}; type PygmentsLanguage = {priority: number; pretty_name: string};
const langs: Record<string, PygmentsLanguage | undefined> = generated_pygments_data.langs; export let langs: Record<string, PygmentsLanguage | undefined> = generated_pygments_data.langs;
export {langs}; export function rewire_langs(value: typeof langs): void {
langs = value;
}

View File

@ -249,10 +249,14 @@ export function get_reaction_sections(message_id: number): JQuery {
return $rows.find(".message_reactions"); 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_section = get_reaction_sections(message_id);
const $reaction = $reaction_section.find(`[data-reaction-id='${CSS.escape(local_id)}']`); const $reaction = $reaction_section.find(`[data-reaction-id='${CSS.escape(local_id)}']`);
return $reaction; 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 { 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; 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"); const $count_element = $reaction.find(".message_reaction_count");
$count_element.text(vote_text); $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_id = event.message_id;
const message = message_store.get(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); message.clean_reactions.set(local_id, clean_reaction_object);
insert_new_reaction(clean_reaction_object, message, user_id); 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, clean_reaction_object: MessageCleanReaction,
message: Message, message: Message,
acting_user_id: number, acting_user_id: number,
): void { ): void => {
// Our caller ensures that this message already has a reaction // Our caller ensures that this message already has a reaction
// for this emoji and sets up our user_list. This function // for this emoji and sets up our user_list. This function
// simply updates the DOM. // simply updates the DOM.
@ -335,13 +347,17 @@ export function update_existing_reaction(
} }
update_vote_text_on_message(message); 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, clean_reaction_object: MessageCleanReaction,
message: Message, message: Message,
user_id: number, user_id: number,
): void { ): void => {
// Our caller ensures we are the first user to react to this // Our caller ensures we are the first user to react to this
// message with this emoji. We then render the emoji/title/count // message with this emoji. We then render the emoji/title/count
// and insert it before the add button. // and insert it before the add button.
@ -389,9 +405,13 @@ export function insert_new_reaction(
} }
update_vote_text_on_message(message); 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 message_id = event.message_id;
const user_id = event.user_id; const user_id = event.user_id;
const message = message_store.get(message_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); update_user_fields(clean_reaction_object, should_display_reactors);
remove_reaction_from_view(clean_reaction_object, message, user_id); 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, clean_reaction_object: MessageCleanReaction,
message: Message, message: Message,
user_id: number, user_id: number,
): void { ): void => {
const local_id = get_local_reaction_id(clean_reaction_object); const local_id = get_local_reaction_id(clean_reaction_object);
const $reaction = find_reaction(message.id, local_id); const $reaction = find_reaction(message.id, local_id);
const reaction_count = clean_reaction_object.user_ids.length; 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); 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[] { 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; 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 // Because whether we display a count or the names of reacting
// users depends on total reactions on the message, we need to // users depends on total reactions on the message, we need to
// recalculate this whenever adjusting reaction rendering on a // 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; message_clean_reaction.vote_text = vote_text;
set_reaction_vote_text(reaction_elem, 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;
} }

View File

@ -930,7 +930,7 @@ export function bulk_inplace_rerender(row_keys: string[]): void {
setTimeout(revive_current_focus, 0); 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()) { if (!recent_view_util.is_visible()) {
return false; return false;
} }
@ -990,6 +990,10 @@ export function inplace_rerender(topic_key: string, is_bulk_rerender?: boolean):
setTimeout(revive_current_focus, 0); setTimeout(revive_current_focus, 0);
} }
return true; 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 { export function update_topic_visibility_policy(stream_id: number, topic: string): boolean {

View File

@ -17,6 +17,11 @@ import * as util from "./util";
// Exported for unit testing // Exported for unit testing
export let is_using_input_method = false; 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; export let search_pill_widget: SearchPillWidget | null = null;
let search_input_has_changed = false; let search_input_has_changed = false;
@ -390,7 +395,7 @@ function reset_searchbox(clear = false): void {
} }
// Exported for tests // 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(); const filter = narrow_state.filter();
if (!filter || filter.is_common_narrow()) { if (!filter || filter.is_common_narrow()) {
// for common narrows, we change the UI (and don't redirect) // 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"); $("#search_query").trigger("blur");
$(".app").trigger("focus"); $(".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); reset_searchbox(clear);
$(".navbar-search").addClass("expanded"); $(".navbar-search").addClass("expanded");
$("#message_view_header").addClass("hidden"); $("#message_view_header").addClass("hidden");
popovers.hide_all(); 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 { export function close_search_bar_and_open_narrow_description(): void {

View File

@ -631,10 +631,10 @@ export function get_input_type($input_elem: JQuery, input_type?: string): string
return input_type; return input_type;
} }
export function get_input_element_value( export let get_input_element_value = (
input_elem: HTMLElement, input_elem: HTMLElement,
input_type?: string, input_type?: string,
): boolean | number | string | null | undefined | GroupSettingValue { ): boolean | number | string | null | undefined | GroupSettingValue => {
const $input_elem = $(input_elem); const $input_elem = $(input_elem);
input_type = get_input_type($input_elem, input_type); input_type = get_input_type($input_elem, input_type);
let input_value; let input_value;
@ -691,6 +691,10 @@ export function get_input_element_value(
default: default:
return undefined; 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( export function set_input_element_value(

View File

@ -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 notification_stream_options = (): dropdown_widget.Option[] => {
const streams = stream_settings_data.get_streams_for_settings_page(); const streams = stream_settings_data.get_streams_for_settings_page();
const options: dropdown_widget.Option[] = streams.map((stream) => ({ 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(); 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( export function register_save_discard_widget_handlers(
@ -1055,7 +1059,7 @@ export function register_save_discard_widget_handlers(
} }
// Exported for tests // Exported for tests
export function initialize_group_setting_widgets(): void { export let initialize_group_setting_widgets = (): void => {
const realm_group_permission_settings = Object.entries( const realm_group_permission_settings = Object.entries(
realm.server_supported_permission_settings.realm, realm.server_supported_permission_settings.realm,
); );
@ -1080,6 +1084,12 @@ export function initialize_group_setting_widgets(): void {
enable_or_disable_group_permission_settings(); enable_or_disable_group_permission_settings();
check_disable_direct_message_initiator_group_widget(); 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 { export function build_page(): void {

View File

@ -217,7 +217,7 @@ export function get_stream_name_from_id(stream_id: number): string {
return get_sub_by_id(stream_id)?.name ?? ""; 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 // Note: Only use this function for situations where
// you are comfortable with a user dealing with an // you are comfortable with a user dealing with an
// old name of a stream (from prior to a rename). // 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); 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 { 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(); 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) { if (stream_id === undefined) {
return DEFAULT_COLOR; return DEFAULT_COLOR;
} }
@ -458,6 +462,10 @@ export function get_color(stream_id: number | undefined): string {
return DEFAULT_COLOR; return DEFAULT_COLOR;
} }
return sub.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 { 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); 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); const sub = sub_store.get(stream_id);
if (sub === undefined || !can_view_subscribers(sub)) { if (sub === undefined || !can_view_subscribers(sub)) {
// If we don't know about the stream, or we ourselves cannot access subscriber list, // 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); 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 { export function create_streams(streams: Stream[]): void {

View File

@ -41,6 +41,10 @@ let zoomed_in = false;
export let stream_cursor: ListCursor<number>; export let stream_cursor: ListCursor<number>;
export function rewire_stream_cursor(value: typeof stream_cursor): void {
stream_cursor = value;
}
let has_scrolled = false; let has_scrolled = false;
export function is_zoomed_in(): boolean { export function is_zoomed_in(): boolean {
@ -97,13 +101,13 @@ export function clear_topics(): void {
zoomed_in = false; zoomed_in = false;
} }
export function update_count_in_dom( export let update_count_in_dom = (
$stream_li: JQuery, $stream_li: JQuery,
stream_counts: StreamCountInfo, stream_counts: StreamCountInfo,
stream_has_any_unread_mention_messages: boolean, stream_has_any_unread_mention_messages: boolean,
stream_has_any_unmuted_unread_mention: boolean, stream_has_any_unmuted_unread_mention: boolean,
stream_has_only_muted_unread_mention: boolean, stream_has_only_muted_unread_mention: boolean,
): void { ): void => {
// The subscription_block properly excludes the topic list, // The subscription_block properly excludes the topic list,
// and it also has sensitive margins related to whether the // and it also has sensitive margins related to whether the
// count is there or not. // 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("has-only-muted-unreads");
$subscription_block.removeClass("stream-with-count"); $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 { 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()) { if (!force_rerender && is_zoomed_in()) {
// We do our best to update topics that are displayed // We do our best to update topics that are displayed
// in case user zoomed in. Streams list will be updated, // 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); 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 { export function update_dom_with_unread_counts(counts: FullUnreadCountsData): void {

View File

@ -58,7 +58,7 @@ export function is_filtering_inactives(): boolean {
return filter_out_inactives; 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 (!filter_out_inactives || sub.pin_to_top) {
// If users don't want to filter inactive streams // If users don't want to filter inactive streams
// to the bottom, we respect that setting and don't // 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 true;
} }
return stream_topic_history.stream_has_topics(sub.stream_id) || sub.newly_subscribed; 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 { export function has_recent_activity_but_muted(sub: StreamSubscription): boolean {

View File

@ -356,10 +356,14 @@ export function has_history_for(stream_id: number): boolean {
return fetched_stream_ids.has(stream_id); 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); const history = find_or_create(stream_id);
return history.get_recent_topic_names(); 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 { export function get_max_message_id(stream_id: number): number {

View File

@ -33,8 +33,11 @@ export type StreamSubscription = z.infer<typeof stream_subscription_schema>;
const subs_by_stream_id = new Map<number, StreamSubscription>(); const subs_by_stream_id = new Map<number, StreamSubscription>();
export function get(stream_id: number): StreamSubscription | undefined { export let get = (stream_id: number): StreamSubscription | undefined =>
return subs_by_stream_id.get(stream_id); 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[] { export function validate_stream_ids(stream_ids: number[]): number[] {

View File

@ -175,7 +175,7 @@ export type TimeRender = {
needs_update: boolean; 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 time_str = "";
let needs_update = false; let needs_update = false;
// render formal time to be used for tippy tooltip // 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, formal_time_str,
needs_update, 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. // Relative time rendering for use in most screens like Recent conversations.

View File

@ -94,7 +94,7 @@ type StreamData = {
subscribed: boolean; subscribed: boolean;
}; };
export function render_typeahead_item(args: { export let render_typeahead_item = (args: {
primary?: string | undefined; primary?: string | undefined;
is_person?: boolean; is_person?: boolean;
img_src?: string; img_src?: string;
@ -105,7 +105,7 @@ export function render_typeahead_item(args: {
is_user_group?: boolean; is_user_group?: boolean;
stream?: StreamData; stream?: StreamData;
emoji_code?: string | undefined; emoji_code?: string | undefined;
}): string { }): string => {
const has_image = args.img_src !== undefined; const has_image = args.img_src !== undefined;
const has_status = args.status_emoji_info !== undefined; const has_status = args.status_emoji_info !== undefined;
const has_secondary = args.secondary !== undefined; const has_secondary = args.secondary !== undefined;
@ -119,9 +119,13 @@ export function render_typeahead_item(args: {
has_secondary_html, has_secondary_html,
has_pronouns, 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") { if (person.type === "broadcast") {
return render_typeahead_item({ return render_typeahead_item({
primary: person.user.special_item_text, primary: person.user.special_item_text,
@ -155,14 +159,21 @@ export function render_person(person: UserPillData | UserOrMentionPillData): str
}; };
return render_typeahead_item(typeahead_arguments); 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 { export let render_user_group = (user_group: {name: string; description: string}): string =>
return render_typeahead_item({ render_typeahead_item({
primary: user_groups.get_display_group_name(user_group.name), primary: user_groups.get_display_group_name(user_group.name),
secondary: user_group.description, secondary: user_group.description,
is_user_group: true, 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( export function render_person_or_user_group(
@ -175,14 +186,17 @@ export function render_person_or_user_group(
return render_person(item); return render_person(item);
} }
export function render_stream(stream: StreamData): string { export let render_stream = (stream: StreamData): string =>
return render_typeahead_item({ render_typeahead_item({
secondary_html: stream.rendered_description, secondary_html: stream.rendered_description,
stream, 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 = { const args = {
is_emoji: true, is_emoji: true,
primary: item.emoji_name.replaceAll("_", " "), primary: item.emoji_name.replaceAll("_", " "),
@ -198,6 +212,10 @@ export function render_emoji(item: EmojiSuggestion): string {
...args, ...args,
emoji_code: item.emoji_code, emoji_code: item.emoji_code,
}); });
};
export function rewire_render_emoji(value: typeof render_emoji): void {
render_emoji = value;
} }
export function sorter<T>(query: string, objs: T[], get_item: (x: T) => string): T[] { export function sorter<T>(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<UserType extends UserOrMentionPillData | UserPillData>({ export let sort_recipients = <UserType extends UserOrMentionPillData | UserPillData>({
users, users,
query, query,
current_stream_id, current_stream_id,
@ -429,7 +447,7 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
current_topic?: string | undefined; current_topic?: string | undefined;
groups?: UserGroupPillData[]; groups?: UserGroupPillData[];
max_num_items?: number | undefined; max_num_items?: number | undefined;
}): (UserType | UserGroupPillData)[] { }): (UserType | UserGroupPillData)[] => {
function sort_relevance(items: UserType[]): UserType[] { function sort_relevance(items: UserType[]): UserType[] {
return sort_people_for_relevance(items, current_stream_id, current_topic); return sort_people_for_relevance(items, current_stream_id, current_topic);
} }
@ -556,6 +574,10 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
// FirstName, which we don't want to artificially prioritize over the // FirstName, which we don't want to artificially prioritize over the
// the lone active user whose name is FirstName LastName. // the lone active user whose name is FirstName LastName.
return recipients.slice(0, max_num_items); return recipients.slice(0, max_num_items);
};
export function rewire_sort_recipients(value: typeof sort_recipients): void {
sort_recipients = value;
} }
export function compare_setting_options( export function compare_setting_options(
@ -618,7 +640,7 @@ export function compare_setting_options(
return 1; return 1;
} }
export function sort_group_setting_options({ export let sort_group_setting_options = ({
users, users,
query, query,
groups, groups,
@ -628,7 +650,7 @@ export function sort_group_setting_options({
query: string; query: string;
groups: UserGroupPillData[]; groups: UserGroupPillData[];
target_group: UserGroup | undefined; target_group: UserGroup | undefined;
}): (UserPillData | UserGroupPillData)[] { }): (UserPillData | UserGroupPillData)[] => {
function sort_group_setting_items( function sort_group_setting_items(
objs: (UserPillData | UserGroupPillData)[], objs: (UserPillData | UserGroupPillData)[],
): (UserPillData | UserGroupPillData)[] { ): (UserPillData | UserGroupPillData)[] {
@ -703,6 +725,10 @@ export function sort_group_setting_options({
} }
return setting_options.slice(0, MAX_ITEMS); 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 = { 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); 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 name_results = typeahead.triage(query, matches, (x) => x.name, compare_by_activity);
const desc_results = typeahead.triage( const desc_results = typeahead.triage(
query, query,
@ -791,11 +817,19 @@ export function sort_streams(matches: StreamPillData[], query: string): StreamPi
); );
return [...name_results.matches, ...desc_results.matches, ...desc_results.rest]; 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); const results = typeahead.triage(query, matches, (x) => x.name, compare_by_name);
return [...results.matches, ...results.rest]; 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( export function query_matches_person(

View File

@ -72,10 +72,15 @@ export function is_user_said_paragraph($element: JQuery): boolean {
return remaining_text.trim() === ":"; return remaining_text.trim() === ":";
} }
export function get_collapsible_status_array($elements: JQuery): boolean[] { export let get_collapsible_status_array = ($elements: JQuery): boolean[] =>
return [...$elements].map( [...$elements].map(
(element) => $(element).is("blockquote") || is_user_said_paragraph($(element)), (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 { export function potentially_collapse_quotes($element: JQuery): boolean {

View File

@ -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(); config.upload_banner(file_id).remove();
if (uppy.getFiles().length === 0) { if (uppy.getFiles().length === 0) {
if (config.mode === "compose") { 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); 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( 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) { if (files.length === 0) {
return; return;
} }
@ -230,6 +234,7 @@ export function upload_files(uppy: Uppy, config: Config, files: File[] | FileLis
file_id, file_id,
true, true,
); );
// eslint-disable-next-line no-loop-func
config.upload_banner_cancel_button(file_id).one("click", () => { config.upload_banner_cancel_button(file_id).one("click", () => {
compose_ui.replace_syntax(get_translated_status(file), "", config.textarea()); compose_ui.replace_syntax(get_translated_status(file), "", config.textarea());
compose_ui.autosize_textarea(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); uppy.removeFile(file_id);
hide_upload_banner(uppy, config, 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", () => { config.upload_banner_hide_button(file_id).one("click", () => {
hide_upload_banner(uppy, config, file_id); 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 { export function setup_upload(config: Config): Uppy {

View File

@ -116,7 +116,7 @@ export function get_user_topics_for_visibility_policy(visibility_policy: number)
return topics; return topics;
} }
export function set_user_topic_visibility_policy( export let set_user_topic_visibility_policy = (
stream_id: number, stream_id: number,
topic: string, topic: string,
visibility_policy: number, visibility_policy: number,
@ -125,7 +125,7 @@ export function set_user_topic_visibility_policy(
$status_element?: JQuery, $status_element?: JQuery,
success_cb?: () => void, success_cb?: () => void,
error_cb?: () => void, error_cb?: () => void,
): void { ): void => {
const data = { const data = {
stream_id, stream_id,
topic, 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 { export function set_visibility_policy_for_element($elt: JQuery, visibility_policy: number): void {

View File

@ -315,7 +315,7 @@ export function get_time_from_date_muted(date_muted: number | undefined): number
return date_muted * 1000; 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 // We previously used setInterval for this purpose, but
// empirically observed that after unsuspend, Chrome can end // empirically observed that after unsuspend, Chrome can end
// up trying to "catch up" by doing dozens of these requests // 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. // exception.
callback(); callback();
}, delay); }, 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] { export function get_string_diff(string1: string, string2: string): [number, number, number] {