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

View File

@ -114,7 +114,7 @@ export function compute_active_status(): ActivityState {
return ActivityState.IDLE;
}
export function send_presence_to_server(redraw?: () => void): void {
export let send_presence_to_server = (redraw?: () => void): void => {
// Zulip has 2 data feeds coming from the server to the client:
// The server_events data, and this presence feed. Data from
// server_events is nicely serialized, but if we've been offline
@ -180,6 +180,10 @@ export function send_presence_to_server(redraw?: () => void): void {
}
},
});
};
export function rewire_send_presence_to_server(value: typeof send_presence_to_server): void {
send_presence_to_server = value;
}
export function mark_client_active(): void {

View File

@ -59,7 +59,7 @@ export function clear_for_testing(): void {
user_filter = undefined;
}
export function update_presence_indicators(): void {
export let update_presence_indicators = (): void => {
$("[data-presence-indicator-user-id]").each(function () {
const user_id = Number.parseInt($(this).attr("data-presence-indicator-user-id") ?? "", 10);
assert(!Number.isNaN(user_id));
@ -68,6 +68,10 @@ export function update_presence_indicators(): void {
.removeClass("user_circle_empty user_circle_green user_circle_idle")
.addClass(user_circle_class);
});
};
export function rewire_update_presence_indicators(value: typeof update_presence_indicators): void {
update_presence_indicators = value;
}
export function redraw_user(user_id: number): void {
@ -115,7 +119,7 @@ export function render_empty_user_list_message_if_needed($container: JQuery): vo
$container.append($(empty_list_widget_html));
}
export function build_user_sidebar(): number[] | undefined {
export let build_user_sidebar = (): number[] | undefined => {
if (realm.realm_presence_disabled) {
return undefined;
}
@ -131,6 +135,10 @@ export function build_user_sidebar(): number[] | undefined {
render_empty_user_list_message_if_needed(buddy_list.$other_users_list);
return all_user_ids; // for testing
};
export function rewire_build_user_sidebar(value: typeof build_user_sidebar): void {
build_user_sidebar = value;
}
function do_update_users_for_search(): void {

View File

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

View File

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

View File

@ -186,7 +186,11 @@ export function defaultSorter(items: string[], query: string): string[] {
return [...beginswith, ...caseSensitive, ...caseInsensitive];
}
export const MAX_ITEMS = 50;
export let MAX_ITEMS = 50;
export function rewire_MAX_ITEMS(value: typeof MAX_ITEMS): void {
MAX_ITEMS = value;
}
/* TYPEAHEAD PUBLIC CLASS DEFINITION
* ================================= */

View File

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

View File

@ -27,8 +27,19 @@ import * as util from "./util";
*/
export const max_size_before_shrinking = 600;
export const max_channel_size_to_show_all_subscribers = 75;
export let max_size_before_shrinking = 600;
export function rewire_max_size_before_shrinking(value: typeof max_size_before_shrinking): void {
max_size_before_shrinking = value;
}
export let max_channel_size_to_show_all_subscribers = 75;
export function rewire_max_channel_size_to_show_all_subscribers(
value: typeof max_channel_size_to_show_all_subscribers,
): void {
max_channel_size_to_show_all_subscribers = value;
}
let is_searching_users = false;
@ -71,11 +82,11 @@ export function level(user_id: number): number {
}
}
export function user_matches_narrow(
export let user_matches_narrow = (
user_id: number,
pm_ids: Set<number>,
stream_id?: number | null,
): boolean {
): boolean => {
if (stream_id) {
return stream_data.is_user_subscribed(stream_id, user_id);
}
@ -83,6 +94,10 @@ export function user_matches_narrow(
return pm_ids.has(user_id) || people.is_my_user_id(user_id);
}
return false;
};
export function rewire_user_matches_narrow(value: typeof user_matches_narrow): void {
user_matches_narrow = value;
}
export function compare_function(

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

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@ -476,7 +476,7 @@ function is_recipient_large_topic(): boolean {
}
// Exported for tests
export function wildcard_mention_policy_authorizes_user(): boolean {
export let wildcard_mention_policy_authorizes_user = (): boolean => {
if (
realm.realm_wildcard_mention_policy ===
settings_config.wildcard_mention_policy_values.by_everyone.code
@ -518,6 +518,12 @@ export function wildcard_mention_policy_authorizes_user(): boolean {
return days >= realm.realm_waiting_period_threshold && !current_user.is_guest;
}
return !current_user.is_guest;
};
export function rewire_wildcard_mention_policy_authorizes_user(
value: typeof wildcard_mention_policy_authorizes_user,
): void {
wildcard_mention_policy_authorizes_user = value;
}
export function stream_wildcard_mention_allowed(): boolean {

View File

@ -106,7 +106,11 @@ export type TypeaheadSuggestion =
| SlashCommandSuggestion;
// We export it to allow tests to mock it.
export const max_num_items = MAX_ITEMS;
export let max_num_items = MAX_ITEMS;
export function rewire_max_num_items(value: typeof max_num_items): void {
max_num_items = value;
}
export let emoji_collection: Emoji[] = [];
@ -389,9 +393,13 @@ function handle_keyup(e: JQuery.KeyUpEvent): void {
}
}
export function split_at_cursor(query: string, $input: JQuery): [string, string] {
export let split_at_cursor = (query: string, $input: JQuery): [string, string] => {
const cursor = $input.caret();
return [query.slice(0, cursor), query.slice(cursor)];
};
export function rewire_split_at_cursor(value: typeof split_at_cursor): void {
split_at_cursor = value;
}
export function tokenize_compose_str(s: string): string {

View File

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

View File

@ -288,14 +288,14 @@ export function is_slash_command(content: string): boolean {
return !content.startsWith("/me") && content.startsWith("/");
}
export function try_deliver_locally(
export let try_deliver_locally = (
message_request: MessageRequest,
insert_new_messages: (
messages: LocalMessage[],
send_by_this_client: boolean,
deliver_locally: boolean,
) => Message[],
): Message | undefined {
): Message | undefined => {
// Checks if the message request can be locally echoed, and if so,
// adds a local echoed copy of the message to appropriate message lists.
//
@ -349,6 +349,10 @@ export function try_deliver_locally(
const message = insert_local_message(message_request, local_id_float, insert_new_messages);
return message;
};
export function rewire_try_deliver_locally(value: typeof try_deliver_locally): void {
try_deliver_locally = value;
}
export function edit_locally(message: Message, request: LocalEditRequest): Message {
@ -432,7 +436,7 @@ export function edit_locally(message: Message, request: LocalEditRequest): Messa
return message;
}
export function reify_message_id(local_id: string, server_id: number): void {
export let reify_message_id = (local_id: string, server_id: number): void => {
const message = echo_state.get_message_waiting_for_id(local_id);
echo_state.remove_message_from_waiting_for_id(local_id);
@ -463,6 +467,10 @@ export function reify_message_id(local_id: string, server_id: number): void {
message_id: message.id,
});
}
};
export function rewire_reify_message_id(value: typeof reify_message_id): void {
reify_message_id = value;
}
export function update_message_lists({old_id, new_id}: {old_id: number; new_id: number}): void {
@ -576,13 +584,17 @@ export function process_from_server(messages: ServerMessage[]): ServerMessage[]
return non_echo_messages;
}
export function message_send_error(message_id: number, error_response: string): void {
export let message_send_error = (message_id: number, error_response: string): void => {
// Error sending message, show inline
const message = message_store.get(message_id)!;
message.failed_request = true;
message.show_slow_send_spinner = false;
show_message_failed(message_id, error_response);
};
export function rewire_message_send_error(value: typeof message_send_error): void {
message_send_error = value;
}
function abort_message(message: Message): void {

View File

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

View File

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

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 () {
let queue: Message[] = [];

View File

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

View File

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

View File

@ -69,7 +69,7 @@ export function potential_subscribers(stream_id: number): User[] {
return people.filter_all_users(is_potential_subscriber);
}
export function get_subscriber_count(stream_id: number, include_bots = true): number {
export let get_subscriber_count = (stream_id: number, include_bots = true): number => {
if (include_bots) {
return get_user_set(stream_id).size;
}
@ -81,6 +81,10 @@ export function get_subscriber_count(stream_id: number, include_bots = true): nu
}
}
return count;
};
export function rewire_get_subscriber_count(value: typeof get_subscriber_count): void {
get_subscriber_count = value;
}
export function get_subscribers(stream_id: number): number[] {

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.
export function get_by_user_id(user_id: number): User {
export let get_by_user_id = (user_id: number): User => {
const person = people_by_user_id_dict.get(user_id);
assert(person, `Unknown user_id in get_by_user_id: ${user_id}`);
return person;
};
export function rewire_get_by_user_id(value: typeof get_by_user_id): void {
get_by_user_id = value;
}
// This is type unsafe version of get_by_user_id for the callers that expects undefined values.
@ -123,7 +127,7 @@ export function validate_user_ids(user_ids: number[]): number[] {
return good_ids;
}
export function get_by_email(email: string): User | undefined {
export let get_by_email = (email: string): User | undefined => {
const person = people_dict.get(email);
if (!person) {
@ -137,6 +141,10 @@ export function get_by_email(email: string): User | undefined {
}
return person;
};
export function rewire_get_by_email(value: typeof get_by_email): void {
get_by_email = value;
}
export function get_bot_owner_user(user: User & {is_bot: true}): User | undefined {
@ -382,7 +390,7 @@ export function emails_strings_to_user_ids_string(emails_string: string): string
return email_list_to_user_ids_string(emails);
}
export function email_list_to_user_ids_string(emails: string[]): string | undefined {
export let email_list_to_user_ids_string = (emails: string[]): string | undefined => {
let user_ids = util.try_parse_as_truthy(
emails.map((email) => {
const person = get_by_email(email);
@ -398,6 +406,12 @@ export function email_list_to_user_ids_string(emails: string[]): string | undefi
user_ids = sort_numerically(user_ids);
return user_ids.join(",");
};
export function rewire_email_list_to_user_ids_string(
value: typeof email_list_to_user_ids_string,
): void {
email_list_to_user_ids_string = value;
}
export function get_full_names_for_poll_option(user_ids: number[]): string {
@ -1056,7 +1070,7 @@ export function get_bot_ids(): number[] {
return bot_ids;
}
export function get_active_human_count(): number {
export let get_active_human_count = (): number => {
let count = 0;
for (const person of active_user_dict.values()) {
if (!person.is_bot) {
@ -1064,6 +1078,10 @@ export function get_active_human_count(): number {
}
}
return count;
};
export function rewire_get_active_human_count(value: typeof get_active_human_count): void {
get_active_human_count = value;
}
export function get_active_user_ids(): number[] {

View File

@ -11,8 +11,12 @@ type PMConversation = {
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);
};
export function rewire_set_partner(value: typeof set_partner): void {
set_partner = value;
}
export function is_partner(user_id: number): boolean {

View File

@ -1,6 +1,8 @@
import generated_pygments_data from "../generated/pygments_data.json";
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");
}
export function find_reaction(message_id: number, local_id: string): JQuery {
export let find_reaction = (message_id: number, local_id: string): JQuery => {
const $reaction_section = get_reaction_sections(message_id);
const $reaction = $reaction_section.find(`[data-reaction-id='${CSS.escape(local_id)}']`);
return $reaction;
};
export function rewire_find_reaction(value: typeof find_reaction): void {
find_reaction = value;
}
export function get_add_reaction_button(message_id: number): JQuery {
@ -261,12 +265,16 @@ export function get_add_reaction_button(message_id: number): JQuery {
return $add_button;
}
export function set_reaction_vote_text($reaction: JQuery, vote_text: string): void {
export let set_reaction_vote_text = ($reaction: JQuery, vote_text: string): void => {
const $count_element = $reaction.find(".message_reaction_count");
$count_element.text(vote_text);
};
export function rewire_set_reaction_vote_text(value: typeof set_reaction_vote_text): void {
set_reaction_vote_text = value;
}
export function add_reaction(event: ReactionEvent): void {
export let add_reaction = (event: ReactionEvent): void => {
const message_id = event.message_id;
const message = message_store.get(message_id);
@ -311,13 +319,17 @@ export function add_reaction(event: ReactionEvent): void {
message.clean_reactions.set(local_id, clean_reaction_object);
insert_new_reaction(clean_reaction_object, message, user_id);
}
};
export function rewire_add_reaction(value: typeof add_reaction): void {
add_reaction = value;
}
export function update_existing_reaction(
export let update_existing_reaction = (
clean_reaction_object: MessageCleanReaction,
message: Message,
acting_user_id: number,
): void {
): void => {
// Our caller ensures that this message already has a reaction
// for this emoji and sets up our user_list. This function
// simply updates the DOM.
@ -335,13 +347,17 @@ export function update_existing_reaction(
}
update_vote_text_on_message(message);
};
export function rewire_update_existing_reaction(value: typeof update_existing_reaction): void {
update_existing_reaction = value;
}
export function insert_new_reaction(
export let insert_new_reaction = (
clean_reaction_object: MessageCleanReaction,
message: Message,
user_id: number,
): void {
): void => {
// Our caller ensures we are the first user to react to this
// message with this emoji. We then render the emoji/title/count
// and insert it before the add button.
@ -389,9 +405,13 @@ export function insert_new_reaction(
}
update_vote_text_on_message(message);
};
export function rewire_insert_new_reaction(value: typeof insert_new_reaction): void {
insert_new_reaction = value;
}
export function remove_reaction(event: ReactionEvent): void {
export let remove_reaction = (event: ReactionEvent): void => {
const message_id = event.message_id;
const user_id = event.user_id;
const message = message_store.get(message_id);
@ -426,13 +446,17 @@ export function remove_reaction(event: ReactionEvent): void {
update_user_fields(clean_reaction_object, should_display_reactors);
remove_reaction_from_view(clean_reaction_object, message, user_id);
};
export function rewire_remove_reaction(value: typeof remove_reaction): void {
remove_reaction = value;
}
export function remove_reaction_from_view(
export let remove_reaction_from_view = (
clean_reaction_object: MessageCleanReaction,
message: Message,
user_id: number,
): void {
): void => {
const local_id = get_local_reaction_id(clean_reaction_object);
const $reaction = find_reaction(message.id, local_id);
const reaction_count = clean_reaction_object.user_ids.length;
@ -466,6 +490,10 @@ export function remove_reaction_from_view(
}
update_vote_text_on_message(message);
};
export function rewire_remove_reaction_from_view(value: typeof remove_reaction_from_view): void {
remove_reaction_from_view = value;
}
export function get_emojis_used_by_user_for_message_id(message_id: number): string[] {
@ -687,7 +715,7 @@ function comma_separated_usernames(user_list: number[]): string {
return comma_separated_usernames;
}
export function update_vote_text_on_message(message: Message): void {
export let update_vote_text_on_message = (message: Message): void => {
// Because whether we display a count or the names of reacting
// users depends on total reactions on the message, we need to
// recalculate this whenever adjusting reaction rendering on a
@ -703,4 +731,10 @@ export function update_vote_text_on_message(message: Message): void {
message_clean_reaction.vote_text = vote_text;
set_reaction_vote_text(reaction_elem, vote_text);
}
};
export function rewire_update_vote_text_on_message(
value: typeof update_vote_text_on_message,
): void {
update_vote_text_on_message = value;
}

View File

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

View File

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

View File

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

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

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 ?? "";
}
export function get_sub_by_name(name: string): StreamSubscription | undefined {
export let get_sub_by_name = (name: string): StreamSubscription | undefined => {
// Note: Only use this function for situations where
// you are comfortable with a user dealing with an
// old name of a stream (from prior to a rename).
@ -230,6 +230,10 @@ export function get_sub_by_name(name: string): StreamSubscription | undefined {
}
return sub_store.get(stream_id);
};
export function rewire_get_sub_by_name(value: typeof get_sub_by_name): void {
get_sub_by_name = value;
}
export function id_to_slug(stream_id: number): string {
@ -449,7 +453,7 @@ export function canonicalized_name(stream_name: string): string {
return stream_name.toString().toLowerCase();
}
export function get_color(stream_id: number | undefined): string {
export let get_color = (stream_id: number | undefined): string => {
if (stream_id === undefined) {
return DEFAULT_COLOR;
}
@ -458,6 +462,10 @@ export function get_color(stream_id: number | undefined): string {
return DEFAULT_COLOR;
}
return sub.color;
};
export function rewire_get_color(value: typeof get_color): void {
get_color = value;
}
export function is_muted(stream_id: number): boolean {
@ -675,7 +683,7 @@ export function is_default_stream_id(stream_id: number): boolean {
return default_stream_ids.has(stream_id);
}
export function is_user_subscribed(stream_id: number, user_id: number): boolean {
export let is_user_subscribed = (stream_id: number, user_id: number): boolean => {
const sub = sub_store.get(stream_id);
if (sub === undefined || !can_view_subscribers(sub)) {
// If we don't know about the stream, or we ourselves cannot access subscriber list,
@ -691,6 +699,10 @@ export function is_user_subscribed(stream_id: number, user_id: number): boolean
}
return peer_data.is_user_subscribed(stream_id, user_id);
};
export function rewire_is_user_subscribed(value: typeof is_user_subscribed): void {
is_user_subscribed = value;
}
export function create_streams(streams: Stream[]): void {

View File

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

View File

@ -58,7 +58,7 @@ export function is_filtering_inactives(): boolean {
return filter_out_inactives;
}
export function has_recent_activity(sub: StreamSubscription): boolean {
export let has_recent_activity = (sub: StreamSubscription): boolean => {
if (!filter_out_inactives || sub.pin_to_top) {
// If users don't want to filter inactive streams
// to the bottom, we respect that setting and don't
@ -70,6 +70,10 @@ export function has_recent_activity(sub: StreamSubscription): boolean {
return true;
}
return stream_topic_history.stream_has_topics(sub.stream_id) || sub.newly_subscribed;
};
export function rewire_has_recent_activity(value: typeof has_recent_activity): void {
has_recent_activity = value;
}
export function has_recent_activity_but_muted(sub: StreamSubscription): boolean {

View File

@ -356,10 +356,14 @@ export function has_history_for(stream_id: number): boolean {
return fetched_stream_ids.has(stream_id);
}
export function get_recent_topic_names(stream_id: number): string[] {
export let get_recent_topic_names = (stream_id: number): string[] => {
const history = find_or_create(stream_id);
return history.get_recent_topic_names();
};
export function rewire_get_recent_topic_names(value: typeof get_recent_topic_names): void {
get_recent_topic_names = value;
}
export function get_max_message_id(stream_id: number): number {

View File

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

View File

@ -175,7 +175,7 @@ export type TimeRender = {
needs_update: boolean;
};
export function render_now(time: Date, today = new Date(), display_year?: boolean): TimeRender {
export let render_now = (time: Date, today = new Date(), display_year?: boolean): TimeRender => {
let time_str = "";
let needs_update = false;
// render formal time to be used for tippy tooltip
@ -210,6 +210,10 @@ export function render_now(time: Date, today = new Date(), display_year?: boolea
formal_time_str,
needs_update,
};
};
export function rewire_render_now(value: typeof render_now): void {
render_now = value;
}
// Relative time rendering for use in most screens like Recent conversations.

View File

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

View File

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

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();
if (uppy.getFiles().length === 0) {
if (config.mode === "compose") {
@ -134,6 +134,10 @@ export function hide_upload_banner(uppy: Uppy, config: Config, file_id: string):
config.send_button().prop("disabled", false);
}
}
};
export function rewire_hide_upload_banner(value: typeof hide_upload_banner): void {
hide_upload_banner = value;
}
function add_upload_banner(
@ -172,7 +176,7 @@ export function show_error_message(
}
}
export function upload_files(uppy: Uppy, config: Config, files: File[] | FileList): void {
export let upload_files = (uppy: Uppy, config: Config, files: File[] | FileList): void => {
if (files.length === 0) {
return;
}
@ -230,6 +234,7 @@ export function upload_files(uppy: Uppy, config: Config, files: File[] | FileLis
file_id,
true,
);
// eslint-disable-next-line no-loop-func
config.upload_banner_cancel_button(file_id).one("click", () => {
compose_ui.replace_syntax(get_translated_status(file), "", config.textarea());
compose_ui.autosize_textarea(config.textarea());
@ -238,10 +243,15 @@ export function upload_files(uppy: Uppy, config: Config, files: File[] | FileLis
uppy.removeFile(file_id);
hide_upload_banner(uppy, config, file_id);
});
// eslint-disable-next-line no-loop-func
config.upload_banner_hide_button(file_id).one("click", () => {
hide_upload_banner(uppy, config, file_id);
});
}
};
export function rewire_upload_files(value: typeof upload_files): void {
upload_files = value;
}
export function setup_upload(config: Config): Uppy {

View File

@ -116,7 +116,7 @@ export function get_user_topics_for_visibility_policy(visibility_policy: number)
return topics;
}
export function set_user_topic_visibility_policy(
export let set_user_topic_visibility_policy = (
stream_id: number,
topic: string,
visibility_policy: number,
@ -125,7 +125,7 @@ export function set_user_topic_visibility_policy(
$status_element?: JQuery,
success_cb?: () => void,
error_cb?: () => void,
): void {
): void => {
const data = {
stream_id,
topic,
@ -203,6 +203,12 @@ export function set_user_topic_visibility_policy(
}
},
});
};
export function rewire_set_user_topic_visibility_policy(
value: typeof set_user_topic_visibility_policy,
): void {
set_user_topic_visibility_policy = value;
}
export function set_visibility_policy_for_element($elt: JQuery, visibility_policy: number): void {

View File

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