diff --git a/tools/test-js-with-node b/tools/test-js-with-node index fd6610e031..86634ef755 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -74,7 +74,7 @@ EXEMPT_FILES = make_set( "web/src/compose_recipient.js", "web/src/compose_reply.js", "web/src/compose_setup.js", - "web/src/compose_state.js", + "web/src/compose_state.ts", "web/src/compose_textarea.ts", "web/src/compose_tooltips.js", "web/src/compose_ui.js", diff --git a/web/src/compose_state.js b/web/src/compose_state.ts similarity index 72% rename from web/src/compose_state.js rename to web/src/compose_state.ts index fa95501a59..dde1aa33cb 100644 --- a/web/src/compose_state.js +++ b/web/src/compose_state.ts @@ -5,7 +5,7 @@ import {$t} from "./i18n"; import * as people from "./people"; import * as sub_store from "./sub_store"; -let message_type = false; // 'stream', 'private', or false-y +let message_type: "stream" | "private" | false = false; let recipient_edited_manually = false; // We use this variable to keep track of whether user has viewed the topic resolved @@ -16,47 +16,51 @@ let recipient_edited_manually = false; // performing these actions let recipient_viewed_topic_resolved_banner = false; -export function set_recipient_edited_manually(flag) { +export function set_recipient_edited_manually(flag: boolean): void { recipient_edited_manually = flag; } -export function is_recipient_edited_manually() { +export function is_recipient_edited_manually(): boolean { return recipient_edited_manually; } -export function set_message_type(msg_type) { +export function set_message_type(msg_type: "stream" | "private" | false): void { message_type = msg_type; } -export function get_message_type() { +export function get_message_type(): "stream" | "private" | false { return message_type; } -export function set_recipient_viewed_topic_resolved_banner(flag) { +export function set_recipient_viewed_topic_resolved_banner(flag: boolean): void { recipient_viewed_topic_resolved_banner = flag; } -export function has_recipient_viewed_topic_resolved_banner() { +export function has_recipient_viewed_topic_resolved_banner(): boolean { return recipient_viewed_topic_resolved_banner; } -export function recipient_has_topics() { +export function recipient_has_topics(): boolean { return message_type !== "stream"; } -export function composing() { +export function composing(): boolean { // This is very similar to get_message_type(), but it returns // a boolean. return Boolean(message_type); } -function get_or_set(input_selector, keep_leading_whitespace, no_trim) { +function get_or_set( + input_selector: string, + keep_leading_whitespace?: boolean, + no_trim?: boolean, +): (newval?: string) => string { // We can't hoist the assignment of '$elem' out of this lambda, // because the DOM element might not exist yet when get_or_set // is called. return function (newval) { - const $elem = $(input_selector); - const oldval = $elem.val(); + const $elem = $(input_selector); + const oldval = $elem.val()!; if (newval !== undefined) { $elem.val(newval); } @@ -73,14 +77,14 @@ function get_or_set(input_selector, keep_leading_whitespace, no_trim) { // "" -> stream message but no stream is selected // integer -> stream id of the selected stream. // "direct" -> Direct message is selected. -export let selected_recipient_id = ""; -export const DIRECT_MESSAGE_ID = "direct"; +export let selected_recipient_id: number | "direct" | "" = ""; +export const DIRECT_MESSAGE_ID = "direct" as const; -export function set_selected_recipient_id(recipient_id) { +export function set_selected_recipient_id(recipient_id: number | "direct" | ""): void { selected_recipient_id = recipient_id; } -export function stream_id() { +export function stream_id(): number | undefined { const stream_id = selected_recipient_id; if (typeof stream_id === "number") { return stream_id; @@ -88,7 +92,7 @@ export function stream_id() { return undefined; } -export function stream_name() { +export function stream_name(): string { const stream_id = selected_recipient_id; if (typeof stream_id === "number") { return sub_store.maybe_get_stream_name(stream_id) || ""; @@ -96,21 +100,18 @@ export function stream_name() { return ""; } -export function set_stream_id(stream_id) { +export function set_stream_id(stream_id: number | ""): void { set_selected_recipient_id(stream_id); } -export function set_compose_recipient_id(recipient_id) { - if (typeof recipient_id !== "number") { - recipient_id = DIRECT_MESSAGE_ID; - } +export function set_compose_recipient_id(recipient_id: number | "direct"): void { set_selected_recipient_id(recipient_id); } // TODO: Break out setter and getter into their own functions. export const topic = get_or_set("input#stream_message_recipient_topic"); -export function empty_topic_placeholder() { +export function empty_topic_placeholder(): string { return $t({defaultMessage: "(no topic)"}); } @@ -120,12 +121,14 @@ export const message_content = get_or_set("textarea#compose-textarea", true); const untrimmed_message_content = get_or_set("textarea#compose-textarea", true, true); -function cursor_at_start_of_whitespace_in_compose() { +function cursor_at_start_of_whitespace_in_compose(): boolean { const cursor_position = $("textarea#compose-textarea").caret(); return message_content() === "" && cursor_position === 0; } -export function focus_in_empty_compose(consider_start_of_whitespace_message_empty = false) { +export function focus_in_empty_compose( + consider_start_of_whitespace_message_empty = false, +): boolean { // A user trying to press arrow keys in an empty compose is mostly // likely trying to navigate messages. This helper function // decides whether the compose box is empty for this purpose. @@ -143,7 +146,7 @@ export function focus_in_empty_compose(consider_start_of_whitespace_message_empt return false; } - const focused_element_id = document.activeElement.id; + const focused_element_id = document.activeElement?.id; if (focused_element_id === "compose-textarea") { // Focus will be in the compose textarea after sending a // message; this is the most common situation. @@ -168,7 +171,9 @@ export function focus_in_empty_compose(consider_start_of_whitespace_message_empt return false; } -export function private_message_recipient(value) { +export function private_message_recipient(): string; +export function private_message_recipient(value: string): undefined; +export function private_message_recipient(value?: string): string | undefined { if (typeof value === "string") { compose_pm_pill.set_from_emails(value); return undefined; @@ -176,18 +181,18 @@ export function private_message_recipient(value) { return compose_pm_pill.get_emails(); } -export function has_message_content() { +export function has_message_content(): boolean { return message_content() !== ""; } -export function has_full_recipient() { +export function has_full_recipient(): boolean { if (message_type === "stream") { return stream_id() !== undefined && topic() !== ""; } return private_message_recipient() !== ""; } -export function update_email(user_id, new_email) { +export function update_email(user_id: number, new_email: string): void { let reply_to = private_message_recipient(); if (!reply_to) { diff --git a/web/tests/drafts.test.js b/web/tests/drafts.test.js index 6e7bbf94df..cab4844719 100644 --- a/web/tests/drafts.test.js +++ b/web/tests/drafts.test.js @@ -195,7 +195,7 @@ test("snapshot_message", ({override_rewire}) => { set_compose_state(); assert.deepEqual(drafts.snapshot_message(), undefined); - curr_draft = {}; + curr_draft = {type: false}; set_compose_state(); assert.equal(drafts.snapshot_message(), undefined); }); @@ -246,7 +246,7 @@ test("remove_old_drafts", () => { }); test("update_draft", ({override, override_rewire}) => { - compose_state.set_message_type(null); + compose_state.set_message_type(false); let draft_id = drafts.update_draft(); assert.equal(draft_id, undefined);